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