﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
36061	Custom data migration for M2M with through_fields not working	Brian Nettleton	Brian Nettleton	"When writing a data migration for a model with a field which uses through fields the through_fields are not honored in the model retrieved from the migration's app registry.

Here is an example of such a data migration using the models from the Django documentation for through_fields.  This data migration has an assert at the end of the forwards function which fails.  Note that issuing the similar instructions in a Django shell works fine, the issue is specific to retrieving a model in a migration using apps.get_model.  The models used and instructions for recreating the problem are also included below.


{{{
# Generated by Django 4.2.17 on 2025-01-02 19:35

from django.db import migrations


def forwards(apps, schema_editor):
    Person = apps.get_model('groups', 'Person')
    Group = apps.get_model('groups', 'Group')
    Group._meta.local_many_to_many[0].remote_field.through_field = (""group"", ""person"")
    Membership = apps.get_model('groups', 'Membership')

    # Initialize some data in the database
    sally, _ = Person.objects.get_or_create(name=""Sally Forth"")
    steve, _ = Person.objects.get_or_create(name=""Steve Smith"")
    alice, _ = Person.objects.get_or_create(name=""Alice Adams"")
    grp1, _ = Group.objects.get_or_create(name=""Group 1"")
    grp2, _ = Group.objects.get_or_create(name=""Group 2"")
    admin, _ = Person.objects.get_or_create(name=""Administrator"")
    Membership.objects.get_or_create(
        group=grp1,
        person=sally,
        inviter=admin,
        invite_reason=""Initial setup via migration""
    )
    Membership.objects.get_or_create(
        group=grp1,
        person=steve,
        inviter=admin,
        invite_reason=""Initial setup via migration""
    )
    Membership.objects.get_or_create(
        group=grp1,
        person=alice,
        inviter=admin,
        invite_reason=""Initial setup via migration""
    )

    # Okay, now also put everyone whose name starts with an ""S"" and is in Group 1 into Group 2
    for s_member in grp1.members.filter(name__startswith=""S""):
        Membership.objects.get_or_create(
            group=grp2,
            person=s_member,
            inviter=admin,
            invite_reason=""Initial setup 2: Put the initial 'S' people from Group 1 into Group 2""
        )

    print(f""\n{grp2.members.count()=}\n"")
    assert grp2.members.count() == 2  # ===== FAILS =====


class Migration(migrations.Migration):

    dependencies = [
        ('groups', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(forwards),
    ]

}}}


The models used for this migration are as follows.


{{{
from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=50)


class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through=""Membership"",
        through_fields=(""group"", ""person""),
    )


class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        related_name=""membership_invites"",
    )
    invite_reason = models.CharField(max_length=64)

}}}


Steps to reproduce:
1. Create a new empty project
2. Create a new empty app called groups and add to INSTALLED_APPS in project settings.py
3. Add models above to groups/models.py
4. Make initial migrations
5. Run initial migrations
6. Create empty migration for groups app
7. Put migration above into empty migration file
8. Run migrate to reproduce the problem

"	Bug	closed	Migrations	4.2	Normal	fixed	through_fields migration	Brian Nettleton	Ready for checkin	1	0	0	0	0	0
