[Answered ]-How to transition from M2M created with 'through' to a M2M managed by Django

1πŸ‘

βœ…

To do this you need to create a migration file that copies the data from the old through table to the new one.

First create an empty migration file that will be populated with our migration operations

python manage.py makemigrations <app> --empty

Then that migration needs to be populated with the following (<app> should be replaced with the app name of the app)

from django.db import migrations, models


def move_courses(apps, schema_editor):
    Student = apps.get_model('<app>', 'Student')
    StudentCourse = apps.get_model('<app>', 'StudentCourse')
    for student in Student.objects.all():
        student.courses.set(
            StudentCourse.objects.filter(student=student).values_list('course', flat=True)
        )


class Migration(migrations.Migration):

    dependencies = [
        ('<app>', '<XXXX_previous_migration_name>'),
    ]

    operations = [
        # Remove the old ManyToManyField
        # This won't delete the through table or it's data
        migrations.RemoveField(
            model_name='Student',
            name='courses',
        ),
        # Add the new ManyToManyField
        migrations.AddField(
            model_name='Student',
            name='courses',
            field=models.ManyToManyField(related_name='students', to='<app>.Course'),
        ),
        # Run a script that copies data from the old through table to the new one
        migrations.RunPython(move_courses),
        # Delete the old through table
        migrations.DeleteModel(
            name='StudentCourse',
        ),
    ]

Then update your models in the way you like:

class Student(models.Model):
    # ... other fields..."
    courses = models.ManyToManyField(
        Course,
        related_name='students',
    )

# Since through='StudentCourse' was removed, below is removed to (keeping it as comment to represent the removal)
# class StudentCourse(models.Model):
#    student = models.ForeignKey(Student, on_delete=models.CASCADE)
#    course = models.ForeignKey(Course, on_delete=models.CASCADE)

class Course(models.Model):
    # ... other fields..."

After models and the empty created migration is updated, you can run migrate

python manage.py migrate

Important: don’t run makemigrations again, only migrate in order for Django to run the migration that moves the data.

Leave a comment