Design layer

How to change the id of an object in Django

How to change the id of an object in Django
In this article:
  1. Change the id of an object using Django migrations
  2. Change the id of an object with dependencies
  3. Change the id through SQL query

Starting a Django project, sometimes, we can't predict some issues we can face during development and especially with the project growing. One of them is the problem that requires changing the id of objects. Some reasons could be changing the type of id, usually integer id to uid, or increasing numbers. The following examples are universal, and you can combine, modify and adapt them as you need based on your project goals.

Change the id of an object using Django migrations

The main problem with replacing ids is that Django creates a new object with the same fields but a new id. That's why we should be careful and take care of old objects. In our case — just delete it.

As a simple example, we will increment each id by one. For this purpose let's create an empty migration file using the standard Django command: python manage.py makemigrations <appname> --empty. And inside it, we add our function, run through each object and change its id.

from django.db import migrations


def change_id(apps, schema_editor):
    ModelName = apps.get_model('appname', 'ModelName')

    # it is important to sort the queryset so that new objects with 
    # a larger id number goes first
    for obj in ModelName.objects.all().order_by('-id'):

        # store the old object's id in a variable
        old_obj_id = obj.id

        # it creates a new object
        obj.id = old_obj_id + 1
        obj.save()

        # and delete the old object
        Model.objects.filter(id=old_obj_id).delete()


class Migration(migrations.Migration):
    dependencies = [
        ('appname', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(change_id),  # Call function here
    ]

This is a fairly safe and simple way to solve the problem. There are no connections to keep and a Django intermediary layer that will prevent you from making a mistake. It is also important that the migration files are created by Django in the familiar python language, which allows us to apply our skills as conveniently and broadly as possible without worrying about how the commands for your particular database will be formed. But, unfortunately, such tasks are quite rare, so let's move on.

Change the id of an object with dependencies

A slightly complicated example of working with a relational model. If the object refers to another model by its field, then there will be no problems with this, the new object will also have this relation. But if we have another model referencing our object, then we have to worry about maintaining this connection.

So, inside our loop, we have to find all objects of the other model that reference ours and rewrite that reference to the newly created one.

def change_id(apps, schema_editor):
    ModelName = apps.get_model('appname', 'ModelName')

    # it is important to sort the queryset so that new objects with 
    # a larger id number goes first
    for obj in ModelName.objects.all().order_by('-id'):

        # store the old object's id in a variable
        old_obj_id = obj.id

        # it creates a new object
        obj.id = old_obj_id + 1
        obj.save()

        # run through all the other model's objects, that has reference to our object
        for ref_model_obj in Model.objects.filter(fk_field_name=old_obj_id):  # NEW
            ref_model_obj.fk_field_name = obj  # NEW
            ref_model_obj.save(update_fields=['fk_field_name'])  # NEW

        # and delete the old object
        Model.objects.filter(id=old_obj_id).delete()

This example uses only one model referenced by our object. But in a real situation, there may be several such models and this should be taken into account. But the flexibility of the python language and the Django framework will also be true helpers here and will help you cope with almost all possible tasks.

Change id through SQL query

Finally, Django allows us to create a raw SQL query for the database, which can allow us to avoid deleting the object. To use this method, you must know SQL (obviously) and have a clear understanding of the tasks, goals, and outcomes you want to achieve. And don't forget to back up your database before experimenting :)

def change_id(apps, schema_editor):
    ModelName = apps.get_model('appname', 'ModelName')

    with connection.cursor() as cursor:

        # it is important to sort the queryset so that new objects with 
        # a larger id number goes first
        for obj in ModelName.objects.all().order_by('-id'):
            cursor.execute(f"UPDATE db_table_name SET id = {obj.id + 1} WHERE id = {obj.id};")

And one more example using SQL query with reference. Before we can change the id of the object, we need to fill the objects that refer to ours with something else. Otherwise, the database will not allow us to replace the id of our object. So, we can set NULL, which we will then replace with the new id of the object. We also need to consider whether the FK field of the referenced model can become NULL and handle this in the query.

def change_id(apps, schema_editor):
    ModelName = apps.get_model('appname', 'ModelName')

    with connection.cursor() as cursor:

        # allow the referenced column to become a nullable
        cursor.execute("ALTER TABLE ref_table_name ALTER COLUMN ref_column DROP NOT NULL;")  # NEW

        # it is important to sort the queryset so that new objects with 
        # a larger id number goes first
        for obj in ModelName.objects.all().order_by('-id'):
            old_obj_id = obj.id  # NEW
            new_object_id = old_obj_id + 1  # NEW

            # set all referenced objects as NULL
            cursor.execute(f"UPDATE ref_table_name SET ref_column is NULL WHERE ref_column = {old_obj_id};")  # NEW

            # change the id of the object
            cursor.execute(f"UPDATE db_table_name SET id = {new_object_id} WHERE id = {old_obj_id};")  # NEW

            # fill all referenced objects with the new object's id
            cursor.execute(f"UPDATE ref_table_name SET ref_column = {new_object_id} WHERE ref_column is NULL;")  # NEW


        # back not nullable for a referenced column if needed
        cursor.execute("ALTER TABLE ref_table_name ALTER COLUMN ref_column SET NOT NULL;")  # NEW

Don't worry about selecting NULL objects to populate with the new id (WHERE ref_column is NULL). Since this field was not nullable, null objects other than the ones we just created should not exist. Otherwise, you must select a different parameter for filtering.

This way of changing the database requires additional knowledge of SQL and particular DB but can be useful in cases where you need to process large amounts of data or work around some Django limitations. So, using raw SQL queries, the framework will not waste time on their formation, and you know for sure that there will be nothing superfluous in the query. It also allows you quickly change the type of field, as we do in the example by making the field nullable and vice versa. Using conventional migrations would require additional effort, time, and lines of code. And therefore, this method can be extremely useful if used correctly.

line
design layer

Looking for an enthusiastic team?