Search code examples
pythondjangoormmigration

A function working in a custom file but not in a "custom migration" on Django


I am beginner in Django and just started learning migrations. In a DB table, I am trying to remove a row that has a specific field value. When I create a custom file which uses Django settings, the function properly deletes the row. But when I use the snippet in another function in a custom migration file, the deletion doesn't take place.

It is possible that I am missing something elementary as a beginner.

So I am trying to delete all orders the status of which is cancelled. The function that works in a separate file is:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "orm_skeleton.settings")
django.setup()

from main_app.models import Order

def delete_test_fnc():
    all_orders = Order.objects.all()

    for i in range(len(all_orders) - 1, -1, -1):
        order = all_orders[i]
        if order.status == "Cancelled":
            order.delete()

delete_test_fnc()

The custom migration where delete doesnt work (other two modifications work without issues) is:

from datetime import timedelta

from django.db import migrations

CURRENT_ORDERS = []

def modify_delivery_warranty(apps, schema_editor):
    global CURRENT_ORDERS
    orders = apps.get_model("main_app", "Order")

    all_orders = orders.objects.all()
    CURRENT_ORDERS = all_orders

    for i in range(len(all_orders) - 1, -1, -1):
        order = all_orders[i]
        if order.status == "Cancelled":
            order.delete()
        elif order.status == "Pending":
            order.delivery = order.order_date + timedelta(days=3)
        elif order.status == "Completed":
            order.warranty = "24 months"
        order.save()


def revert_modify_delivery_warranty(apps, schema_editor):
    orders = apps.get_model("main_app", "Order")
    orders.objects.all().delete()

    for order in CURRENT_ORDERS:
        new_order = orders.objects.create(
            product_name=order.get("product_name"),
            customer_name=order.get("customer_name"),
            order_date=order.get("order_date"),
            amount=order.get("amount"),
            product_price=order.get("product_price"),
            total_price=order.get("total_price"),
            warranty=order.get("warranty"),
            delivery=order.get("delivery")
        )
        new_order.save()


class Migration(migrations.Migration):

    dependencies = [
        ('main_app', '0021_order'),`your text`
    ]

    operations = [migrations.RunPython(
        modify_delivery_warranty, 
        reverse_code=revert_modify_delivery_warranty
    )
    ]

Solution

  • The problem with your code is that if the status is Cancelled, you call order.delete() which deletes it from the database. But the Python object itself is still in the memory in variable order.. After all if statements, you call order.save() which saves the object back to database. Which means your object is indeed deleted but it is saved again.

    So, a way to fix it is by calling order.save() only when needed:

    for i in range(len(all_orders) - 1, -1, -1):
        order = all_orders[i]
        if order.status == "Cancelled":
            order.delete()
        if order.status == "Pending":
            order.delivery = order.order_date + timedelta(days=3)
        elif order.status == "Completed":
            order.warranty = "24 months"
    
        if order.status != "Cancelled":
            order.save()
    

    Another important note is that the code you implemented will not revert back the deleted objects because once you run python manage.py migrate and it stops, all in-memory objects are deleted after the process exists. Which means when you try to revert the migration, the CURRENT_ORDERS will be empty. For more about this: https://docs.djangoproject.com/en/4.2/topics/migrations/