Django: How to migrate from ManyToMany to ForeignKey?

EDIT First create a DB backup

First create a new temporary FK relationship

_city = models.ForeignKey(...)

and make a migration

python manage.py makemigration
python manage.py migrate

You then need to create a new data migration in the relevant app by creating an empty migration file:

python manage.py makemigration --empty myhouseapp

and in that file manually assign the new relationship from the M2M to the FK. It will look something like:

from django.db import migrations


def save_city_fk(apps, schema):
    City = apps.get_model('myapp', 'City')
    House = apps.get_model('myapp', 'House')
    for house in House.objects.all():
        house._city = house.cities.all().first()  # Or whatever criterea you want
        house._city.save()


def save_city_m2m(apps, schema):
    # This should do the reverse
    City = apps.get_model('myapp', 'City')
    House = apps.get_model('myapp', 'House')
    for house in House.objects.all():
        if house._city:
            house.cities.add(house._city)


class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.RunPython(save_city_fk, save_city_m2m)
    ]

Remove the M2M field, and create another migration.

python manage.py makemigrations

Rename the FK from _city to city and create a final migration

python manage.py makemigrations

Then migrate:

python manage.py migrate

Based on Timmy's answer here is what I did:

  1. I added a field like this city = models.ForeignKey(City, related_name='has_houses', blank=True, null=True) to avoid the related_name for reverse relations and to have the FK blank. Then I ran makemigrations and migrate.

  2. Next, I ran python manage.py makemigrations --empty houses because my app is named houses.

  3. I added the code for the migration (see below). Then I ran migrate.

  4. I deleted the M2M field and the related_name, null and blank constraints and ran makemigrations and migrate one last time.

Code for the migration:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.15 on 2019-02-15 09:09
from __future__ import unicode_literals

from django.db import migrations


def save_city_fk(apps, schema):
    City = apps.get_model('cities', 'City')
    House = apps.get_model('houses', 'House')
    for house in House.objects.all():
        house.city = house.cities.all().first()
        house.save()


class Migration(migrations.Migration):

    dependencies = [
        ('houses', '0003_auto_20190215_0949'),
    ]

    operations = [
        migrations.RunPython(save_city_fk),
    ]