Can't get proper response from `issubclass()` when called with Django's `__fake__` model type inside migration

Question:

I’m trying to generate UUIDs for some models in a migration. The problem is that the models returned from apps.get_app_config(app_name).get_models() are these __fake__ objects, they are what Django calls historical models, so calling issubclass(fake_model, UUIDModelMixin) returns False when I am expecting True.

Is there anyway to determine what parent classes these historical model objects actually inherited from?

Relevant Django docs:
https://docs.djangoproject.com/en/3.1/topics/migrations/#historical-models

And here is the full function being called in a migration:

from os.path import basename, dirname
import uuid
from common.models.uuid_mixin import UUIDModelMixin

def gen_uuid(apps, schema_editor):
    app_name = basename(dirname(dirname(__file__)))
    models = apps.get_app_config(app_name).get_models()

    uuid_models = [m for m in models if issubclass(m, UUIDModelMixin)]

    for model in uuid_models:
        for row in model.objects.all():
            row.uuid = uuid.uuid4()
            row.save(update_fields=['uuid'])

Here is my UUIDModelMixin code:

class UUIDModelMixin(models.Model):
    """
    `uuid` field will be auto set with uuid4 values
    """
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)

    @property
    def short_uuid(self):
        return truncatechars(self.uuid, 8)

    class Meta:
        abstract = True
Asked By: steezeburger

||

Answers:

To determine the parent classes of the historical models use apps.get_model method. It’s about the actual model class, rather than the historical model object.

from os.path import basename, dirname
import uuid
from common.models.uuid_mixin import UUIDModelMixin

def gen_uuid(apps, schema_editor):
    app_name = basename(dirname(dirname(__file__)))
    models = apps.get_app_config(app_name).get_models()

    uuid_models = []
    for model in models:
        actual_model = apps.get_model(app_label=app_name, model_name=model._meta.model_name)
        if issubclass(actual_model, UUIDModelMixin):
            uuid_models.append(actual_model)

    for model in uuid_models:
        for row in model.objects.all():
            row.uuid = uuid.uuid4()
            row.save(update_fields=['uuid'])
Answered By: BobSfougaroudis

I don’t think it’s correct to stick to checking model mixin on the historical model because migrations don’t record which abstract base class your model inherits from at any given point in time and cannot really trace back that in any way. Suppose that your model inherits from MixinX in mutation 0001 but then you change it to MixinY. First of all no new migration will be generated through makemigrations. Second you can even delete MixinX so this should not really impact your existing migrations.

Instead, you can stick to checking fields (even your own field types) because the specific type of the field is recorded inside migrations. For example if we change django.db.models.UUIDField to MyUUIDField which inherits it then makemigrations creates new migration for that and the code below would be able to find the specific type of the field:

def gen_uuid(apps, schema_editor):
    app_name = basename(dirname(dirname(__file__)))
    models = apps.get_app_config(app_name).get_models()
    for model in models:
        uuid_fields = []
        for field in model._meta.get_fields():
            if not isinstance(field, UUIDField):  # or custom MyUUIDField
                continue
            uuid_fields.append(field)
        if not uuid_fields:
            continue
        for row in model.objects.all():
            for uuid_field in uuid_fields:
                setattr(row, uuid_field.get_attname(), uuid.uuid4())
            row.save(update_fields=[f.get_attname() for f in uuid_fields])

There is such statement in the Django docs:

In addition, the concrete base classes of the model are stored as pointers, so you must always keep base classes around for as long as there is a migration that contains a reference to them.

It actually means that if your base model is non-abstract then it will be stored in migration and probably there is the way to verify class hierarchy. However if the mixin is an abstract base model then it won’t. That’s why I would suggest to stick to checking field types in this case.

Answered By: bellum
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.