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:
- 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
- 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
- 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
- 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),
]
- Run the new data migration, as follows:
(env)$ python manage.py migrate ideas
- 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
- 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