Migrations reflect not only database but some business logic. Migrations blow up

Let us suppose that we had a model (example is from the documentation https://docs.djangoproject.com/en/4.1/ref/models/fields/#filefield):

def user_directory_path(instance, filename):
    # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
    return 'user_{0}/{1}'.format(instance.user.id, filename)

class MyModel(models.Model):
    upload = models.FileField(upload_to=user_directory_path)

Migrations will be something like:

('upload', models.FileField(upload_to=vocabulary_phrases.models.user_directory_path)),

But now you decide to change to a class:

class UploadTo:
    def __init__(self, folder, filename_suffix=""):
        self.folder = folder
        self.filename_suffix = filename_suffix

    def _get_filename(self, instance, filename):
        _, file_extension = os.path.splitext(filename)
        result = str(instance.a_uuid)

        if self.filename_suffix:
            result += "-{}".format(self.filename_suffix)

        result += file_extension

        return result

    def save_path(self, instance, filename):
        tmp_filename = self._get_filename(instance, filename)
        result = "{}/{}".format(self.folder.value,
                                tmp_filename)
        return result


class MyModel(models.Model):
    upload = UploadTo(folder=UPLOAD_TO.VOCABULARY_FILE_FOLDER, filename_suffix="trololo").save_path

When you try to makemigrations, this code will blow. It will complain like 0002_migration contains user_directory_path, it is absent.

This change in the code not mechanical. I can't imagine that it can be done just by refactor / rename in IDE. Then I can't imagine what the new migration should look like. This means that I will not be able to modify migrations file easily.

I will have to deploy another project next to this one and another database. Delete all migrations, makemigrations, copy created migration and substitute all occurrances of user_directory_path with what is in my clipboard.

Complicated, I would say. And I suspect then it may be error prone.

This was just one example. Django hardcodes something in migrations which I would say is related more to business logics than to the database itself. Let us not discuss whether this example is good or not. It illustrates how something from our code is hardcoded in migrations and Django fails automatically make new migrations.

Could you recommend me what is the best practice of coping with this problem?

A simple solution would be to run

python manage.py makemigrations

while you still have user_directory_path in your file

eg:

import os

from django.db import models


def user_directory_path(instance, filename):
    # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
    return "user_{0}/{1}".format(instance.user.id, filename)


class UploadTo:
    def __init__(self, folder, filename_suffix=""):
        self.folder = folder
        self.filename_suffix = filename_suffix

    def _get_filename(self, instance, filename):
        _, file_extension = os.path.splitext(filename)
        result = str(instance.a_uuid)

        if self.filename_suffix:
            result += "-{}".format(self.filename_suffix)

        result += file_extension

        return result

    def save_path(self, instance, filename):
        tmp_filename = self._get_filename(instance, filename)
        result = "{}/{}".format(self.folder.value, tmp_filename)
        return result


class UPLOAD_TO:
    VOCABULARY_FILE_FOLDER = "asd"


class MyModel(models.Model):
    upload = UploadTo(
        folder=UPLOAD_TO.VOCABULARY_FILE_FOLDER, filename_suffix="trololo"
    ).save_path

then you can squash migrations and delete the function which will avoid the need to deploy a copy project

Back to Top