Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Django 3 Web Development Cookbook
Django 3 Web Development Cookbook

Django 3 Web Development Cookbook: Actionable solutions to common problems in Python web development , Fourth Edition

Arrow left icon
Profile Icon Aidas Bendoraitis Profile Icon Jake Kronika
Arrow right icon
€29.99
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.6 (9 Ratings)
Paperback Mar 2020 608 pages 4th Edition
eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m
Arrow left icon
Profile Icon Aidas Bendoraitis Profile Icon Jake Kronika
Arrow right icon
€29.99
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.6 (9 Ratings)
Paperback Mar 2020 608 pages 4th Edition
eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m
eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Django 3 Web Development Cookbook

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

Introduction

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.

Technical requirements

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.

Getting ready

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 itself, possibly in a base.py file.

How to do it...

Open the models.py file of any Django app that you want to use mixins with, and type the following code:

# myproject/apps/ideas/models.py
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.models import (
CreationModificationDateBase,
MetaTagsBase,
UrlBase,
)

class Idea(CreationModificationDateBase, MetaTagsBase, UrlBase):
title = models.CharField(
_("Title"),
max_length=200,
)
content = models.TextField(
_("Content"),
)
# other fields…

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title

def get_url_path(self):
return reverse("idea_details", kwargs={
"idea_id": str(self.pk),
})

How it works...

Django's model inheritance supports three types of inheritance: abstract base classes, multi-table inheritance, and proxy models. Model mixins are abstract model classes, in that we define them by using an abstract Meta class, with specified fields, properties, and methods. When you create a model such as Idea, as shown in the preceding example, it inherits all of the features from CreationModificationDateMixin, MetaTagsMixin, and UrlMixin. All of the fields of these abstract classes are saved in the same database table as the fields of the extending model. In the following recipes, you will learn how to define your model mixins.

There's more...

In normal Python class inheritance, if there is more than one base class, and all of them implement a specific method, and you call that method on the instance of a child class, only the method from the first parent class will be called, as in the following example:

>>> class A(object):
... def test(self):
... print("A.test() called")
...

>>> class B(object):
... def test(self):
... print("B.test() called")
...

>>> class C(object):
... def test(self):
... print("C.test() called")
...

>>> class D(A, B, C):
... def test(self):
... super().test()
... print("D.test() called")

>>> d = D()
>>> d.test()
A.test() called
D.test() called

This is the same for Django model base classes; however, there is one special exception.

The Django framework does some magic with metaclasses that calls the save() and delete() methods from each of the base classes.

That means that you can confidently do pre-save, post-save, pre-delete, and post-delete manipulations for specific fields defined specifically in the mixin by overwriting the save() and delete() methods.

To learn more about the different types of model inheritance, refer to the official Django documentation, available at https://docs.djangoproject.com/en/2.2/topics/db/models/#model-inheritance.

See also

  • The Creating a model mixin with URL-related methods recipe
  • The Creating a model mixin to handle creation and modification dates recipe
  • The Creating a model mixin to take care of meta tags recipe

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

Getting ready

If you haven't yet done so, create the myproject.apps.core app where you will store your model mixins. Then, create a models.py file in the core package. Alternatively, if you create a reusable app, put the mixins in a base.py file in that app.

How to do it...

Execute the following steps, one by one:

  1. Add the following content to the models.py file of your core app:
# myproject/apps/core/models.py
from urllib.parse import urlparse, urlunparse
from django.conf import settings
from django.db import models

class UrlBase(models.Model):
"""
A replacement for get_absolute_url()
Models extending this mixin should have either get_url or
get_url_path implemented.
"""
class Meta:
abstract = True

def get_url(self):
if hasattr(self.get_url_path, "dont_recurse"):
raise NotImplementedError
try:
path = self.get_url_path()
except NotImplementedError:
raise
return settings.WEBSITE_URL + path
get_url.dont_recurse = True

def get_url_path(self):
if hasattr(self.get_url, "dont_recurse"):
raise NotImplementedError
try:
url = self.get_url()
except NotImplementedError:
raise
bits = urlparse(url)
return urlunparse(("", "") + bits[2:])
get_url_path.dont_recurse = True

def get_absolute_url(self):
return self.get_url()
  1. Add the WEBSITE_URL setting without a trailing slash to the dev, test, staging, and production settings. For example, for the development environment this will be as follows:
# myproject/settings/dev.py
from
._base import *

DEBUG = True
WEBSITE_URL = "http://127.0.0.1:8000" # without trailing slash
  1. To use the mixin in your app, import the mixin from the core app, inherit the mixin in your model class, and define the get_url_path() method, as follows:
# myproject/apps/ideas/models.py
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.models import UrlBase

class Idea(UrlBase):
# fields, attributes, properties and methods…

def get_url_path(self):
return reverse("idea_details", kwargs={
"idea_id": str(self.pk),
})

How it works...

The UrlBase class is an abstract model that has three methods, as follows:

  • get_url() retrieves the full URL of the object.
  • get_url_path() retrieves the absolute path of the object.
  • get_absolute_url() mimics the get_url_path() method.

The get_url() and get_url_path() methods are expected to be overwritten in the extended model class, for example, Idea. You can define get_url(), and get_url_path() will strip it to the path. Alternatively, you can define get_url_path(), and get_url() will prepend the website URL to the beginning of the path.

The rule of thumb is to always overwrite the get_url_path() method.

In the templates, use get_url_path() when you need a link to an object on the same website, as follows:

<a href="{{ idea.get_url_path }}">{{ idea.title }}</a>

Use get_url() for links in external communication, such as in emails, RSS feeds, or APIs; an example is as follows:

<a href="{{ 
idea.get_url }}">{{ idea.title }}</a>

The default get_absolute_url() method will be used in the Django model administration for the View on site functionality, and might also be used by some third-party Django apps.

There's more...

In general, don't use incremental primary keys in the URLs, because it is not safe to expose them to the end user: the total amount of items would be visible, and it would be too easy to navigate through different items by just changing the URL path.

You can use the primary keys in the URLs for the detail pages only if they are Universal Unique Identifiers (UUIDs) or generated random strings. Otherwise, create and use a slug field, as follows:

class Idea(UrlBase):
slug = models.SlugField(_("Slug for URLs"), max_length=50)

See also

  • The Using model mixins recipe
  • The Creating a model mixin to handle creation and modification dates recipe
  • The Creating a model mixin to take care of meta tags recipe
  • The Creating a model mixin to handle generic relations recipe
  • The Configuring settings for development, testing, staging, and production environments recipe, in Chapter 1, Getting Started with Django 3.0

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.

Getting ready

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...

Open the models.py file in your myprojects.apps.core package, and insert the following content there:

# myproject/apps/core/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _


class CreationModificationDateBase(models.Model):
"""
Abstract base class with a creation and modification date and time
"""

created = models.DateTimeField(
_("Creation Date and Time"),
auto_now_add=True,
)

modified = models.DateTimeField(
_("Modification Date and Time"),
auto_now=True,
)

class Meta:
abstract = True

How it works...

The CreationModificationDateMixin class is an abstract model, which means that extending model classes will create all of the fields in the same database table—that is, there will be no one-to-one relationships that make the table more complex to handle.

This mixin has two date-time fields, created and modified. With the auto_now_add and auto_now attributes, the timestamps will be saved automatically when saving a model instance. The fields will automatically get the editable=False attribute, and thus will be hidden in administration forms. If USE_TZ is set to True in the settings (which is the default and recommended), time-zone-aware timestamps will be used. Otherwise, time-zone-naive timestamps will be used. Timezone-aware timestamps are saved in the Coordinated Universal Time (UTC) time zone in the database and converted to the default time zone of the project when reading or writing them. Time-zone-naive timestamps are saved in the local time zone of the project in the database; they are not practical to use in general, because they make time management between time zones more complicated.

To make use of this mixin, we just have to import it and extend our model, as follows:

# myproject/apps/ideas/models.py
from django.db import models

from myproject.apps.core.models import CreationModificationDateBase

class Idea(CreationModificationDateBase):
# other fields, attributes, properties, and methods…

See also

  • The Using model mixins recipe
  • The Creating a model mixin to take care of meta tags recipe
  • The Creating a model mixin to handle generic relations recipe

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.

Getting ready

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 of that, create a meta.html file to store the basic meta tag markup.

How to do it...

Let's create our model mixin:

  1. Make sure to add "myproject.apps.core" to INSTALLED_APPS in the settings, because we want to take the templates directory into account for this module.
  2. Add the following basic meta tag markup to meta_field.html:
{# templates/core/includes/meta_field.html #}
<meta name="{{ name }}" content="{{ content }}" />
  1. Open the models.py file from the core package in your favorite editor, and add the following content:
# myproject/apps/core/models.py
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string


class MetaTagsBase(models.Model):
"""
Abstract base class for generating meta tags
"""
meta_keywords = models.CharField(
_("Keywords"),
max_length=255,
blank=True,
help_text=_("Separate keywords with commas."),
)
meta_description = models.CharField(
_("Description"),
max_length=255,
blank=True,
)
meta_author = models.CharField(
_("Author"),
max_length=255,
blank=True,
)
meta_copyright = models.CharField(
_("Copyright"),
max_length=255,
blank=True,
)

class Meta:
abstract = True

def get_meta_field(self, name, content):
tag = ""
if name and content:
tag = render_to_string("core/includes/meta_field.html",
{
"name": name,
"content": content,
})
return mark_safe(tag)

def get_meta_keywords(self):
return self.get_meta_field("keywords", self.meta_keywords)

def get_meta_description(self):
return self.get_meta_field("description",
self.meta_description)

def get_meta_author(self):
return self.get_meta_field("author", self.meta_author)

def get_meta_copyright(self):
return self.get_meta_field("copyright",
self.meta_copyright)

def get_meta_tags(self):
return mark_safe("\n".join((
self.get_meta_keywords(),
self.get_meta_description(),
self.get_meta_author(),
self.get_meta_copyright(),
)))

How it works...

This mixin adds four fields to the model that extends from it: meta_keywords, meta_description, meta_author, and meta_copyright. The corresponding get_*() methods, used to render the associated meta tags, are also added. Each of these passes the name and appropriate field content to the core get_meta_field() method, which uses this input to return rendered markup based on the meta_field.html template. Finally, a shortcut get_meta_tags() method is provided to generate the combined markup for all of the available metadata at once.

If you use this mixin in a model, such as Idea, which is shown in the Using model mixins recipe at the start of this chapter, you can put the following in the HEAD section of your detail page template to render all of the meta tags at once, as follows:

{% block meta_tags %}
{{ block.super }}
{{ idea.get_meta_tags }}
{% endblock %}

Here, a meta_tags block has been defined in a parent template, and this snippet shows how the child template redefines the block, including the content from the parent first as block.super, and extending it with our additional tags from the idea object. You could also render only a specific meta tag by using something like the following: {{ idea.get_meta_description }}.

As you may have noticed from the models.py code, the rendered meta tags are marked as safe – that is, they are not escaped, and we don't need to use the safe template filter. Only the values that come from the database are escaped, in order to guarantee that the final HTML is well formed. The database data in meta_keywords and other fields will automatically be escaped when we call render_to_string() for the meta_field.html template, because that template does not specify {% autoescape off %} in its content.

See also

  • The Using model mixins recipe
  • The Creating a model mixin to handle creation and modification dates recipe
  • The Creating a model mixin to handle generic relations recipe
  • The Arranging the base.html template recipe in Chapter 4, Templates and JavaScript

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.

Getting ready

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:

# myproject/settings/_base.py

INSTALLED_APPS = [
# contributed
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# third-party
# ...
# local
"myproject.apps.core",
"myproject.apps.categories",
"myproject.apps.ideas",
]

Again, make sure that you have already created the myproject.apps.core app for your model mixins.

How to do it...

To create and use a mixin for generic relations follow these steps:

  1. Open the models.py file in the core package in a text editor, and insert the following content there:
# myproject/apps/core/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldError


def object_relation_base_factory(
prefix=None,
prefix_verbose=None,
add_related_name=False,
limit_content_type_choices_to=None,
is_required=False):
"""
Returns a mixin class for generic foreign keys using
"Content type - object ID" with dynamic field names.
This function is just a class generator.

Parameters:
prefix: a prefix, which is added in front of
the fields
prefix_verbose: a verbose name of the prefix, used to
generate a title for the field column
of the content object in the Admin
add_related_name: a boolean value indicating, that a
related name for the generated content
type foreign key should be added. This
value should be true, if you use more
than one ObjectRelationBase in your
model.

The model fields are created using this naming scheme:
<<prefix>>_content_type
<<prefix>>_object_id
<<prefix>>_content_object
"""
p = ""
if prefix:
p = f"{prefix}_"

prefix_verbose = prefix_verbose or _("Related object")
limit_content_type_choices_to = limit_content_type_choices_to
or {}

content_type_field = f"{p}content_type"
object_id_field = f"{p}object_id"
content_object_field = f"{p}content_object"

class TheClass(models.Model):
class Meta:
abstract = True

if add_related_name:
if not prefix:
raise FieldError("if add_related_name is set to "
"True, a prefix must be given")
related_name = prefix
else:
related_name = None

optional = not is_required

ct_verbose_name = _(f"{prefix_verbose}'s type (model)")

content_type = models.ForeignKey(
ContentType,
verbose_name=ct_verbose_name,
related_name=related_name,
blank=optional,
null=optional,
help_text=_("Please select the type (model) "
"for the relation, you want to build."),
limit_choices_to=limit_content_type_choices_to,
on_delete=models.CASCADE)

fk_verbose_name = prefix_verbose

object_id = models.CharField(
fk_verbose_name,
blank=optional,
null=False,
help_text=_("Please enter the ID of the related object."),
max_length=255,
default="") # for migrations

content_object = GenericForeignKey(
ct_field=content_type_field,
fk_field=object_id_field)

TheClass.add_to_class(content_type_field, content_type)
TheClass.add_to_class(object_id_field, object_id)
TheClass.add_to_class(content_object_field, content_object)

return TheClass
  1. The following code snippet is an example of how to use two generic relationships in your app (put this code in ideas/models.py):
# myproject/apps/ideas/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.models import (
object_relation_base_factory as generic_relation,
)


FavoriteObjectBase = generic_relation(
is_required=True,
)


OwnerBase = generic_relation(
prefix="owner",
prefix_verbose=_("Owner"),
is_required=True,
add_related_name=True,
limit_content_type_choices_to={
"model__in": (
"user",
"group",
)
}
)


class Like(FavoriteObjectBase, OwnerBase):
class Meta:
verbose_name = _("Like")
verbose_name_plural = _("Likes")

def __str__(self):
return _("{owner} likes {object}").format(
owner=self.owner_content_object,
object=self.content_object
)

How it works...

As you can see, this snippet is more complex than the previous ones.

The object_relation_base_factory function, which we have aliased to generic_relation, for short, in our import, is not a mixin itself; it is a function that generates a model mixin – that is, an abstract model class to extend from. The dynamically created mixin adds the content_type and object_id fields and the content_object generic foreign key that points to the related instance.

Why can't we just define a simple model mixin with these three attributes? A dynamically generated abstract class allows us to have prefixes for each field name; therefore, we can have more than one generic relation in the same model. For example, the Like model, which was shown previously, will have the content_type, object_id, and content_object fields for the favorite object, and owner_content_type, owner_object_id, and owner_content_object for the one (user or group) that liked the object.

The object_relation_base_factory function, which we have aliased
to generic_relation for short, adds the possibility to limit the content type choices by the limit_content_type_choices_to parameter. The preceding example limits the choices for owner_content_type to only the content types of the User and Group models.

See also

  • The Creating a model mixin with URL-related methods recipe
  • The Creating a model mixin to handle creation and modification dates recipe
  • The Creating a model mixin to take care of meta tags recipe
  • The Implementing the Like widget recipe in Chapter 4, Templates and JavaScript

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 migrations for all multilingual models.

Getting ready

Have you created the myproject.apps.core package used in the preceding recipes of this chapter? You will now need a new model_fields.py file within the core app, for the custom model fields.

How to do it...

Execute the following steps to define the multilingual character field and multilingual text field:

  1. Open the model_fields.py file, and create the base multilingual field, as follows:
# myproject/apps/core/model_fields.py
from django.conf import settings
from django.db import models
from django.utils.translation import get_language
from django.utils import translation

class MultilingualField(models.Field):
SUPPORTED_FIELD_TYPES = [models.CharField, models.TextField]

def __init__(self, verbose_name=None, **kwargs):
self.localized_field_model = None
for model in MultilingualField.SUPPORTED_FIELD_TYPES:
if issubclass(self.__class__, model):
self.localized_field_model = model
self._blank = kwargs.get("blank", False)
self._editable = kwargs.get("editable", True)
super().__init__(verbose_name, **kwargs)

@staticmethod
def localized_field_name(name, lang_code):
lang_code_safe = lang_code.replace("-", "_")
return f"{name}_{lang_code_safe}"

def get_localized_field(self, lang_code, lang_name):
_blank = (self._blank
if lang_code == settings.LANGUAGE_CODE
else True)
localized_field = self.localized_field_model(
f"{self.verbose_name} ({lang_name})",
name=self.name,
primary_key=self.primary_key,
max_length=self.max_length,
unique=self.unique,
blank=_blank,
null=False, # we ignore the null argument!
db_index=self.db_index,
default=self.default or "",
editable=self._editable,
serialize=self.serialize,
choices=self.choices,
help_text=self.help_text,
db_column=None,
db_tablespace=self.db_tablespace)
return localized_field

def contribute_to_class(self, cls, name,
private_only=False,
virtual_only=False):
def translated_value(self):
language = get_language()
val = self.__dict__.get(
MultilingualField.localized_field_name(
name, language))
if not val:
val = self.__dict__.get(
MultilingualField.localized_field_name(
name, settings.LANGUAGE_CODE))
return val

# generate language-specific fields dynamically
if not cls._meta.abstract:
if self.localized_field_model:
for lang_code, lang_name in settings.LANGUAGES:
localized_field = self.get_localized_field(
lang_code, lang_name)
localized_field.contribute_to_class(
cls,
MultilingualField.localized_field_name(
name, lang_code))

setattr(cls, name, property(translated_value))
else:
super().contribute_to_class(
cls, name, private_only, virtual_only)
  1. In the same file, subclass the base field for character and text field forms, as follows:
class MultilingualCharField(models.CharField, MultilingualField):
pass

class MultilingualTextField(models.TextField, MultilingualField):
pass
  1. Create an admin.py file in the core app, and add the following content:
# myproject/apps/core/admin.py
from django.conf import settings

def get_multilingual_field_names(field_name):
lang_code_underscored = settings.LANGUAGE_CODE.replace("-",
"_")
field_names = [f"{field_name}_{lang_code_underscored}"]
for lang_code, lang_name in settings.LANGUAGES:
if lang_code != settings.LANGUAGE_CODE:
lang_code_underscored = lang_code.replace("-", "_")
field_names.append(
f"{field_name}_{lang_code_underscored}"
)
return field_names

Now, we'll consider an example of how to use the multilingual fields in your app, as follows:

  1. First, set multiple languages in the settings for your project. Let's say, our website will support all official languages of the European Union, with English being the default language:
# myproject/settings/_base.py
LANGUAGE_CODE = "en"

# All official languages of European Union
LANGUAGES = [
("bg", "Bulgarian"), ("hr", "Croatian"),
("cs", "Czech"), ("da", "Danish"),
("nl", "Dutch"), ("en", "English"),
("et", "Estonian"), ("fi", "Finnish"),
("fr", "French"), ("de", "German"),
("el", "Greek"), ("hu", "Hungarian"),
("ga", "Irish"), ("it", "Italian"),
("lv", "Latvian"), ("lt", "Lithuanian"),
("mt", "Maltese"), ("pl", "Polish"),
("pt", "Portuguese"), ("ro", "Romanian"),
("sk", "Slovak"), ("sl", "Slovene"),
("es", "Spanish"), ("sv", "Swedish"),
]
  1. Then, open the models.py file from the myproject.apps.ideas app, and create the multilingual fields for the Idea model, as follows:
# myproject/apps/ideas/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.model_fields import (
MultilingualCharField,
MultilingualTextField,
)

class Idea(models.Model):
title = MultilingualCharField(
_("Title"),
max_length=200,
)
content = MultilingualTextField(
_("Content"),
)

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title
  1. Create an admin.py file for the ideas app:
# myproject/apps/ideas/admin.py
from
django.contrib import admin
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.admin import get_multilingual_field_names

from .models import Idea

@admin.register(Idea)
class IdeaAdmin(admin.ModelAdmin):
fieldsets = [
(_("Title and Content"), {
"fields": get_multilingual_field_names("title") +
get_multilingual_field_names("content")
}),
]

How it works...

The example of Idea will generate a model that is similar to the following:

class Idea(models.Model):
title_bg = models.CharField(
_("Title (Bulgarian)"),
max_length=200,
)
title_hr = models.CharField(
_("Title (Croatian)"),
max_length=200,
)
# titles for other languages…
title_sv = models.CharField(
_("Title (Swedish)"),
max_length=200,
)

content_bg = MultilingualTextField(
_("Content (Bulgarian)"),
)
content_hr = MultilingualTextField(
_("Content (Croatian)"),
)
# content for other languages…
content_sv = MultilingualTextField(
_("Content (Swedish)"),
)

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title

If there were any language codes with a dash, like "de-ch" for Swiss German, the fields for those languages would be replaced with underscores, like title_de_ch and content_de_ch.

In addition to the generated language-specific fields, there will be two properties – title and content – that will return the corresponding field in the currently active language. These will fall back to the default language if no localized field content is available.

The MultilingualCharField and MultilingualTextField fields will juggle the model fields dynamically, depending on your LANGUAGES setting. They will overwrite the contribute_to_class() method that is used when the Django framework creates the model classes. The multilingual fields dynamically add character or text fields for each language of the project. You'll need to create a database migration to add the appropriate fields in the database. Also, the properties are created to return the translated value of the currently active language or the main language, by default.

In the administration, get_multilingual_field_names() will return a list of language-specific field names, starting with one of the default languages and then proceeding with the other languages from the LANGUAGES setting.

Here are a couple of examples of how you might use the multilingual fields in templates and views.

If you have the following code in the template, it will show the text in the currently active language, let's say Lithuanian, and will fall back to English if the translation doesn't exist:

<h1>{{ idea.title }}</h1>
<div>{{ idea.content|urlize|linebreaks }}</div>

If you want to have your QuerySet ordered by the translated titles, you can define it as follows:

>>> lang_code = input("Enter language code: ")
>>> lang_code_underscored = lang_code.replace("-", "_")
>>> qs = Idea.objects.order_by(f"title_{lang_code_underscored}")

See also

  • The Working with model translation tables recipe
  • The Using migrations recipe
  • Chapter 6, Model Administration

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.

Getting ready

Once again, we will start with the myprojects.apps.core app.

How to do it...

Execute the following steps to prepare for multilingual models:

  1. In the core app, create model_fields.py with the following content:
# myproject/apps/core/model_fields.py
from
django.conf import settings
from django.utils.translation import get_language
from django.utils import translation

class TranslatedField(object):
def __init__(self, field_name):
self.field_name = field_name

def __get__(self, instance, owner):
lang_code = translation.get_language()
if lang_code == settings.LANGUAGE_CODE:
# The fields of the default language are in the main
model
return getattr(instance, self.field_name)
else:
# The fields of the other languages are in the
translation
# model, but falls back to the main model
translations = instance.translations.filter(
language=lang_code,
).first() or instance
return getattr(translations, self.field_name)
  1. Add the admin.py file to the core app with the following content:
# myproject/apps/core/admin.py
from
django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _

class LanguageChoicesForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
LANGUAGES_EXCEPT_THE_DEFAULT = [
(lang_code, lang_name)
for lang_code, lang_name in settings.LANGUAGES
if lang_code != settings.LANGUAGE_CODE
]
super().__init__(*args, **kwargs)
self.fields["language"] = forms.ChoiceField(
label=_("Language"),
choices=LANGUAGES_EXCEPT_THE_DEFAULT,
required=True,
)

Now let's implement the multilingual models:

  1. First, set multiple languages in the settings for your project. Let's say, our website will support all official languages of European Union with English being the default language:
# myproject/settings/_base.py
LANGUAGE_CODE = "en"

# All official languages of European Union
LANGUAGES = [
("bg", "Bulgarian"), ("hr", "Croatian"),
("cs", "Czech"), ("da", "Danish"),
("nl", "Dutch"), ("en", "English"),
("et", "Estonian"), ("fi", "Finnish"),
("fr", "French"), ("de", "German"),
("el", "Greek"), ("hu", "Hungarian"),
("ga", "Irish"), ("it", "Italian"),
("lv", "Latvian"), ("lt", "Lithuanian"),
("mt", "Maltese"), ("pl", "Polish"),
("pt", "Portuguese"), ("ro", "Romanian"),
("sk", "Slovak"), ("sl", "Slovene"),
("es", "Spanish"), ("sv", "Swedish"),
]
  1. Then, let's create the Idea and IdeaTranslations models:
# myproject/apps/ideas/models.py
from
django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.model_fields import TranslatedField


class Idea(models.Model):
title = models.CharField(
_("Title"),
max_length=200,
)
content = models.TextField(
_("Content"),
)
translated_title = TranslatedField("title")
translated_content = TranslatedField("content")

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title


class IdeaTranslations(models.Model):
idea = models.ForeignKey(
Idea,
verbose_name=_("Idea"),
on_delete=models.CASCADE,
related_name="translations",
)
language = models.CharField(_("Language"), max_length=7)

title = models.CharField(
_("Title"),
max_length=200,
)
content = models.TextField(
_("Content"),
)

class Meta:
verbose_name = _("Idea Translations")
verbose_name_plural = _("Idea Translations")
ordering = ["language"]
unique_together = [["idea", "language"]]

def __str__(self):
return self.title
  1. Last, create the admin.py for the ideas app as follows:
# myproject/apps/ideas/admin.py
from
django.contrib import admin
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.admin import LanguageChoicesForm

from .models import Idea, IdeaTranslations


class IdeaTranslationsForm(LanguageChoicesForm):
class Meta:
model = IdeaTranslations
fields = "__all__"


class IdeaTranslationsInline(admin.StackedInline):
form = IdeaTranslationsForm
model = IdeaTranslations
extra = 0


@admin.register(Idea)
class IdeaAdmin(admin.ModelAdmin):
inlines = [IdeaTranslationsInline]

fieldsets = [
(_("Title and Content"), {
"fields": ["title", "content"]
}),
]

How it works...

We keep the language-specific fields of the default language in the Idea model itself. The translations for each language are in the IdeaTranslations model, which will be listed in the administration as an inline translation. IdeaTranslations don't have the language choices at the model for a reason – we don't want to create migrations every time a new language is added or some language is removed. Instead, the language choices are set in the administration form, also making sure that the default language is skipped or not available for selection in the list. The language choices are restricted using the LanguageChoicesForm class.

To get a specific field in the current language, you would use the fields defined as TranslatedField. In the template, that would look like the following:

<h1>{{ idea.translated_title }}</h1>
<div>{{ idea.translated_content|urlize|linebreaks }}</div>

To order items by a translated title in a specific language, you would use the annotate() method as follows:

>>> from django.conf import settings
>>> from django.db import models
>>> lang_code = input("Enter language code: ")

>>> if lang_code == settings.LANGUAGE_CODE:
... qs = Idea.objects.annotate(
... title_translation=models.F("title"),
... content_translation=models.F("content"),
... )
... else:
... qs = Idea.objects.filter(
... translations__language=lang_code,
... ).annotate(
... title_translation=models.F("translations__title"),
... content_translation=models.F("translations__content"),
... )

>>> qs = qs.order_by("title_translation")

>>> for idea in qs:
... print(idea.title_translation)

In this example, we prompt for a language code in the Django shell. If the language is the default one, we store the title and content as the title_translation and the content_translation from the Idea model. If there is another language chosen, we read the title and content as title_translation and content_translation from the IdeaTranslations model with the chosen language.

Afterward, we can filter or order QuerySet by title_translation or content_translation.

See also

  • The Handling multilingual fields recipe
  • Chapter 6, Model Administration

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.

Getting ready

Let's work with categories and ideas apps to illustrate how to deal with cross dependencies.

How to do it...

Follow these practices when working with models that use models from other apps:

  1. For foreign keys and many-to-many relationships with models from other apps, use the "<app_label>.<model>" declaration instead of importing the model. In Django this works with ForeignKey, OneToOneField, and ManyToManyField, for example:
# myproject/apps/ideas/models.py
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _

class
Idea(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("Author"),
on_delete=models.SET_NULL,
blank=True,
null=True,
)
category = models.ForeignKey(
"categories.Category",
verbose_name=_("Category"),
blank=True,
null=True,
on_delete=models.SET_NULL,
)
# other fields, attributes, properties and methods…

Here, settings.AUTH_USER_MODEL is a setting with a value such as "auth.User":

  1. If you need to access a model from another app in a method, import that model inside the method instead of at the module level, for example, as follows:
# myproject/apps/categories/models.py
from
django.db import models
from django.utils.translation import gettext_lazy as _

class Category(models.Model):
# fields, attributes, properties, and methods…

def get_ideas_without_this_category(self):
from myproject.apps.ideas.models import Idea
return Idea.objects.exclude(category=self)
  1. If you use model inheritance, for example, for model mixins, keep the base classes in a separate app and place them before other apps that would use them in INSTALLED_APPS, as follows:
# myproject/settings/_base.py

INSTALLED_APPS = [
# contributed
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# third-party
# ...
# local
"myproject.apps.core",
"myproject.apps.categories",
"myproject.apps.ideas",
]

Here the ideas app will use the model mixins from the core app as follows:

# myproject/apps/ideas/models.py
from
django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.models import (
CreationModificationDateBase,
MetaTagsBase,
UrlBase,
)

class Idea(CreationModificationDateBase, MetaTagsBase, UrlBase):
# fields, attributes, properties, and methods…

See also

  • The Configuring settings for development, testing, staging, and production environments recipe in Chapter 1, Getting Started with Django 3.0
  • The Respecting the import order in Python files recipe in Chapter 1, Getting Started with Django 3.0
  • The Using model mixins recipe
  • The Changing the foreign key to the many-to-many field recipe

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.

Getting ready

Let's start with the ideas app and the Idea model that will have at least title and author fields.

How to do it...

Set the database constraints in the Meta class of the Idea model as follows:

# myproject/apps/ideas/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _


class Idea(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("Author"),
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name="authored_ideas",
)
title = models.CharField(
_("Title"),
max_length=200,
)

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")
constraints = [
models.UniqueConstraint(
fields=["title"],
condition=~models.Q(author=None),
name="unique_titles_for_each_author",
),
models.CheckConstraint(
check=models.Q(
title__iregex=r"^\S.*\S$"
# starts with non-whitespace,
# ends with non-whitespace,
# anything in the middle
),
name="title_has_no_leading_and_trailing_whitespaces",
)
]

How it works...

We define two constraints in the database.

The first one, UniqueConstraint, tells the titles to be unique for each author. If the author is not set, the titles can be repeated. To check if the author is set we use the negated lookup: ~models.Q(author=None). Note that in Django, the ~ operator for lookups is equivalent to the exclude() method of a QuerySet, so these QuerySets are equivalent:

ideas_with_authors = Idea.objects.exclude(author=None)
ideas_with_authors2 = Idea.objects.filter(~models.Q(author=None))

The second constraint, CheckConstraint, checks if the title doesn't start and end with a whitespace. For that, we use a regular expression lookup.

There's more...

Database constraints don't affect form validation. They will just raise django.db.utils.IntegrityError if any data doesn't pass its conditions when saving entries to the database.

If you want to have data validated at the forms, you have to implement the validation in addition yourself, for example, in the clean() method of the model. That would look like this for the Idea model:

# myproject/apps/ideas/models.py
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class Idea(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("Author"),
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name="authored_ideas2",
)
title = models.CharField(
_("Title"),
max_length=200,
)

# other fields and attributes…

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")
constraints = [
models.UniqueConstraint(
fields=["title"],
condition=~models.Q(author=None),
name="unique_titles_for_each_author2",
),
models.CheckConstraint(
check=models.Q(
title__iregex=r"^\S.*\S$"
# starts with non-whitespace,
# ends with non-whitespace,
# anything in the middle
),
name="title_has_no_leading_and_trailing_whitespaces2",
)
]

def clean(self):
import re
if self.author and Idea.objects.exclude(pk=self.pk).filter(
author=self.author,
title=self.title,
).exists():
raise ValidationError(
_("Each idea of the same user should have a unique title.")
)
if not re.match(r"^\S.*\S$", self.title):
raise ValidationError(
_("The title cannot start or end with a whitespace.")
)

# other properties and methods…

See also

  • Chapter 3, Forms and Views
  • The Using database query expressions recipe in Chapter 10, Bells and Whistles

Using migrations

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.

Getting ready

Activate your virtual environment in the command-line tool, and change the active directory to your project's directory.

How to do it...

To create the database migrations, take a look at the following steps:

  1. When you create models in your new categories or ideas app, you have to create an initial migration that will create the database tables for your app. This can be done by using the following command:
(env)$ python manage.py makemigrations ideas
  1. The first time that you want to create all of the tables for your project, run the following command:
(env)$ python manage.py migrate

Run this command when you want to execute the new migrations for all of your apps.

  1. If you want to execute the migrations for a specific app, run the following command:
(env)$ python manage.py migrate ideas
  1. If you make some changes in the database schema, you will have to create a migration for that schema. For example, if we add a new subtitle field to the idea model, we can create the migration by using the following command:
(env)$ python manage.py makemigrations --name=subtitle_added ideas

However, the --name=subtitle_added field can be skipped because in most cases Django generates fairly self-explanatory default names.

  1. Sometimes, you may have to add to or change data in the existing schema in bulk, which can be done with a data migration, instead of a schema migration. To create a data migration that modifies the data in the database table, we can use the following command:
(env)$ python manage.py makemigrations --name=populate_subtitle \
> --empty ideas

The --empty parameter tells Django to create a skeleton data migration, which you have to modify to perform the necessary data manipulation before applying it. For data migrations, setting the name is recommended.

  1. To list all of the available applied and unapplied migrations, run the following command:
(env)$ python manage.py showmigrations

The applied migrations will be listed with an [X] prefix. The unapplied ones will be listed with a [ ] prefix.

  1. To list all of the available migrations for a specific app, run the same command, but pass the app name, as follows:
(env)$ python manage.py showmigrations ideas

How it works...

Django migrations are instruction files for the database migration mechanism. The instruction files inform us about which database tables to create or remove, which fields to add or remove, and which data to insert, update, or delete. Also they define which migrations are dependent on which other migrations.

There are two types of migrations in Django. One is schema migration, and the other is data migration. Schema migration should be created when you add new models, or add or remove fields. Data migration should be used when you want to fill the database with some values or massively delete values from the database. Data migrations should be created by using a command in the command-line tool, and then coded in the migration file.

The migrations for each app are saved in their migrations directories. The first migration will usually be called 0001_initial.py, and the other migrations in our example app will be called 0002_subtitle_added.py and 0003_populate_subtitle.py. Each migration gets a number prefix that is automatically incremented. For each migration that is executed, there is an entry that is saved in the django_migrations database table.

It is possible to migrate back and forth by specifying the number of the migration to which we want to migrate, as shown in the following command:

(env)$ python manage.py migrate ideas 0002

To unmigrate all migrations of the app including the initial migration, run the following:

(env)$ python manage.py migrate ideas zero

Unmigrating requires each migration to have both a forward and a backward action. Ideally, the backward action would undo exactly the changes made by the forward action. However, in some cases such a change would be unrecoverable, such as when the forward action has removed a column from the schema, because it will have destroyed data. In such a case, the backward action might restore the schema, but the data would remain lost forever, or else there might not be a backward action at all.

Do not commit your migrations to version control until you have tested the forward and backward migration process and you are sure that they will work well in other developments and public website environments.

There's more...

See also

  • The Working with a virtual environment recipe in Chapter 1, Getting Started with Django 3.0
  • The Working with Docker containers for Django, Gunicorn, Nginx, and PostgreSQL recipe in Chapter 1, Getting Started with Django 3.0
  • The Handling project dependencies with pip receipe in Chapter 1, Getting Started with Django 3.0
  • The Including external dependencies in your project recipe in Chapter 1, Getting Started with Django 3.0
  • The Changing a foreign key to the many-to-many field recipe

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.

Getting ready

Let's suppose that you have the Idea model, with a foreign key pointing to the Category model.

  1. Let's define the Category model in the categories app, as follows:
# myproject/apps/categories/models.py
from
django.db import models
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.model_fields import MultilingualCharField


class Category(models.Model):
title = MultilingualCharField(
_("Title"),
max_length=200,
)

class Meta:
verbose_name = _("Category")
verbose_name_plural = _("Categories")

def __str__(self):
return self.title
  1. Let's define the Idea model in the ideas app, as follows:
# myproject/apps/ideas/models.py
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.model_fields import (
MultilingualCharField,
MultilingualTextField,
)

class Idea(models.Model):
title = MultilingualCharField(
_("Title"),
max_length=200,
)
content = MultilingualTextField(
_("Content"),
)
category = models.ForeignKey(
"categories.Category",
verbose_name=_("Category"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="category_ideas",
)

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title
  1. Create and execute initial migrations by using the following commands:
(env)$ python manage.py makemigrations categories
(env)$ python manage.py makemigrations ideas
(env)$ python manage.py migrate

How to do it...

The following steps will show you how to switch from a foreign key relation to a many-to-many relation, while preserving the already existing data:

  1. Add a new many-to-many field, called categories, as follows:
# myproject/apps/ideas/models.py
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.model_fields import (
MultilingualCharField,
MultilingualTextField,
)

class Idea(models.Model):
title = MultilingualCharField(
_("Title"),
max_length=200,
)
content = MultilingualTextField(
_("Content"),
)
category = models.ForeignKey(
"categories.Category",
verbose_name=_("Category"),
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="category_ideas",
)
categories = models.ManyToManyField(
"categories.Category",
verbose_name=_("Categories"),
blank=True,
related_name="ideas",
)

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title
  1. Create and run a schema migration, in order to add the new relationship to the database, as shown in the following code snippet:
(env)$ python manage.py makemigrations ideas
(env)$ python manage.py migrate ideas
  1. Create a data migration to copy the categories from the foreign key to the many-to-many field, as follows:
(env)$ python manage.py makemigrations --empty \
> --name=copy_categories ideas
  1. Open the newly created migration file (0003_copy_categories.py), and define the forward migration instructions, as shown in the following code snippet:
# myproject/apps/ideas/migrations/0003_copy_categories.py
from django.db import migrations


def copy_categories(apps, schema_editor):
Idea = apps.get_model("ideas", "Idea")
for idea in Idea.objects.all():
if idea.category:
idea.categories.add(idea.category)


class Migration(migrations.Migration):

dependencies = [
('ideas', '0002_idea_categories'),
]

operations = [
migrations.RunPython(copy_categories),
]
  1. Run the new data migration, as follows:
(env)$ python manage.py migrate ideas
  1. Delete the foreign key category field in the models.py file, leaving only the new categories many-to-many field, as follows:
# myproject/apps/ideas/models.py
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from myproject.apps.core.model_fields import (
MultilingualCharField,
MultilingualTextField,
)

class Idea(models.Model):
title = MultilingualCharField(
_("Title"),
max_length=200,
)
content = MultilingualTextField(
_("Content"),
)

categories = models.ManyToManyField(
"categories.Category",
verbose_name=_("Categories"),
blank=True,
related_name="ideas",
)

class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")

def __str__(self):
return self.title
  1. Create and run a schema migration, in order to delete the Categories field from the database table, as follows:
(env)$ python manage.py makemigrations ideas
(env)$ python manage.py migrate ideas

How it works...

At first, we add a new many-to-many field to the Idea model, and a migration is generated to update the database accordingly. Then, we create a data migration that will copy the existing relations from the foreign key category to the new many-to-many categories. Lastly, we remove the foreign key field from the model, and update the database once more.

There's more...

Our data migration currently includes only the forward action, copying the foreign
key category as the first related item in the new categories relationship. Although we did not elaborate here, in a real-world scenario it would be best to include the reverse operation as well. This could be accomplished by copying the first related item back to the category foreign key. Unfortunately, any Idea object with multiple categories would lose extra data.

See also

  • The Using migrations recipe
  • The Handling multilingual fields recipe
  • The Working with model translation tables recipe
  • The Avoiding circular dependencies recipe
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Explore the latest version of Django, and learn effectively with the help of practical examples
  • Follow a task-based approach to develop professional web apps using Django and Python
  • Discover recipes to enhance the security and performance of your apps

Description

Django is a web framework for perfectionists with deadlines, designed to help you build manageable medium and large web projects in a short time span. This fourth edition of the Django Web Development Cookbook is updated with Django 3's latest features to guide you effectively through the development process. This Django book starts by helping you create a virtual environment and project structure for building Python web apps. You'll learn how to build models, views, forms, and templates for your web apps and then integrate JavaScript in your Django apps to add more features. As you advance, you'll create responsive multilingual websites, ready to be shared on social networks. The book will take you through uploading and processing images, rendering data in HTML5, PDF, and Excel, using and creating APIs, and navigating different data types in Django. You'll become well-versed in security best practices and caching techniques to enhance your website's security and speed. This edition not only helps you work with the PostgreSQL database but also the MySQL database. You'll also discover advanced recipes for using Django with Docker and Ansible in development, staging, and production environments. By the end of this book, you will have become proficient in using Django's powerful features and will be equipped to create robust websites.

Who is this book for?

This Django book is for Python web developers who want to build fast and secure web apps that can scale over time. You'll also find this book useful if you are looking to upgrade to the latest Django 3 framework. Prior experience of working with the Django framework is required.

What you will learn

  • Discover how to set the basic configurations to start any Django project
  • Understand full-stack web application development using Django
  • Build a database structure using reusable model mixins
  • Implement security, performance, and deployment features in your web apps
  • Import data from local sources and external web services and export it to your app
  • Secure web applications against malicious usage and find and fix common performance bottlenecks
Estimated delivery fee Deliver to Denmark

Premium delivery 7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Mar 23, 2020
Length: 608 pages
Edition : 4th
Language : English
ISBN-13 : 9781838987428
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Denmark

Premium delivery 7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Publication date : Mar 23, 2020
Length: 608 pages
Edition : 4th
Language : English
ISBN-13 : 9781838987428
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 124.97
Django 3 By Example
€41.99
Django 3 Web Development Cookbook
€29.99
Web Development with Django
€52.99
Total 124.97 Stars icon
Banner background image

Table of Contents

14 Chapters
Getting Started with Django 3.0 Chevron down icon Chevron up icon
Models and Database Structure Chevron down icon Chevron up icon
Forms and Views Chevron down icon Chevron up icon
Templates and JavaScript Chevron down icon Chevron up icon
Custom Template Filters and Tags Chevron down icon Chevron up icon
Model Administration Chevron down icon Chevron up icon
Security and Performance Chevron down icon Chevron up icon
Hierarchical Structures Chevron down icon Chevron up icon
Importing and Exporting Data Chevron down icon Chevron up icon
Bells and Whistles Chevron down icon Chevron up icon
Testing Chevron down icon Chevron up icon
Deployment Chevron down icon Chevron up icon
Maintenance Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.6
(9 Ratings)
5 star 44.4%
4 star 11.1%
3 star 22.2%
2 star 0%
1 star 22.2%
Filter icon Filter
Top Reviews

Filter reviews by




James Jul 01, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Great book if you are currently learning about Django. Can highly recommend, helped me with my studies.
Amazon Verified review Amazon
William H Scholtz Nov 20, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I decided to move my skill level from Ruby/Rails, PHP Zend world to Python/Django world. (Python has become such a popular language)In the process, I worked through multiple books, most of them targeted at the absolute beginner. I bought this by "accident", one my best accidents in a long time. This book gives you recipes for each and all any developer will need to build powerful applications, as well as deploy and manage the deployment servers. The recipes are excellent and to the point. Over the past 25 years as a developer, this is probably the most comprehensive manual I have ever encountered. Not for the beginner, for sure, but all Django developers will eventually need a book like this. Thanks to the authors. It is a "steal" on Kindle for $15. I was getting frustrated with most other books/manuals available. This book solved that problem...
Amazon Verified review Amazon
Alex Sep 17, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This is more than what I was expecting! If you're an experienced web developer and you just need a kickstart with Django development - this is it. Just check "Table on content" - you'll get a very good idea of what's inside.Big thank you to the authors! Great work
Amazon Verified review Amazon
Martin May 05, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
The book gave me a pretty good current (April 2020) Overview of Django and Django Rest Framework. As both technologies are still under heavy development finding print books is not easy. It should still be read with a Django Documentation looked up every now and then.
Amazon Verified review Amazon
ChiTownTimmay Jun 04, 2020
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
If you’re looking for a more detailed explanation of Django concepts, this book builds up your understanding more completely. My path with Django started with the initial Django documentation, and, while good, just was a lot to handle. Then I came across this book and more fully/completely was able to make those connections to concepts better. While I definitely wouldn’t start with this book or use it as a primer, if you’re looking to upskill or increase your understanding of Django, it’s definitely worth a spot on your bookshelf.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact [email protected] with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at [email protected] using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on [email protected] with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on [email protected] within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on [email protected] who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on [email protected] within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela