Search code examples
djangopostgresqldjango-modelsdatabase-migrationdjango-migrations

Django: How to migrate from ManyToMany to ForeignKey?


I'm building a REST API and server using Django and Django Rest Framework. We are using a postgres database.

I need to simplify a badly designed relation. We have a model (House) that has a ManyToMany relationship to another (City). In reality it would be enough when this is a ForeignKey relationship.

I googled and couldn't find any blog posts or docs how to properly migrate in this direction. I could only find the other way (FK to M2M).

I'm 98% sure that all the data on the server will be consistent with a FK relationship (meaning I'm pretty sure all houses only have one city). We need to change the relationship for several reasons and aren't able to keep the M2M.

I'm afraid to just change the model and running makemigrations and migrate. I was wondering, how do you properly migrate from M2M to FK? Are there any caveats I have to take into account? How can I deal with data if surprisingly there are houses with multiple city's? The data set is still quite small (less than 10k entries) if that matters.

Thank you very much.


Solution

  • EDIT First create a DB backup

    First create a new temporary FK relationship

    _city = models.ForeignKey(...)
    

    and make a migration

    python manage.py makemigrations
    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 makemigrations --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.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