In my djangoapp, the initial migration creates a model called UserProfile with a field called lowest_priority_to_alert. There is then later a migration that loads in some fixture data using a custom RunPython migration so that it can be used in our e2e tests. Finally there is a migration that removes the lowest_priority_to_alert field.
When I run these migrations, I can see that the initial migration is run, then the migration to load in the fixture data. However, I get the following error: AttributeError: 'UserProfile' object has no attribute 'lowest_priority_to_alert'
I will show my migration bellow but I have added some logging, the first to see which fields the UserProfile model currently has and the second to see what the fixture data is. All this looks normal:
['id', 'deleted_at', 'account_type', 'job_title', 'timezone', 'locale', 'api_only', 'api_burst_rate', 'api_sustained_rate', 'saved_frontend_settings_v2', 'lowest_priority_to_alert', 'mobile_push_notifications', 'dark_hour_start', 'dark_hour_end', 'is_fake', 'is_hidden', 'mobile_tabbar_options', 'menu_options', 'fixed_frontend_settings', 'disable_screen_recording', 'business', 'group', 'user', 'clock_format', 'csv_delimiter', 'temperature_unit', 'is_superstaff_user', 'set_of_cards', 'webapp_cards_v3', 'feature_flags']
[
{
  "model": "collector.userprofile",
  "pk": 1,
  "fields": {
    "deleted_at": null,
    "user": 1,
    "business": 795,
    "group": null,
    "account_type": 1,
    "job_title": null,
    "timezone": "Europe/London",
    "locale": "en-GB",
    "api_only": false,
    "api_burst_rate": 200,
    "api_sustained_rate": 10000,
    "saved_frontend_settings_v2": {
      "sales": {
        "tax": 0,
        "comps": 0,
        "promos": 0,
        "service": 0
      },
      "labour": {
        "showSalaried": 1,
        "labourOverhead": 0,
        "spreadSalaried": 0
      }
    },
    "lowest_priority_to_alert": 3000,
    "mobile_push_notifications": true,
    "dark_hour_start": null,
    "dark_hour_end": null,
    "is_superstaff_user": false,
    "is_fake": false,
    "is_hidden": false,
    "mobile_tabbar_options": [
      "home",
      "sales",
      "social",
      "employee",
      "more"
    ],
    "menu_options": [],
    "fixed_frontend_settings": null,
    "disable_screen_recording": false,
    "temperature_unit": null,
    "clock_format": null,
    "csv_delimiter": null,
    "webapp_cards_v3": [],
    "set_of_cards": [],
    "feature_flags": []
  }
}
]
I am very confused as to what is happening. I have triple checked that my migrations are running in the correct order. Here is my migration:
from pathlib import Path
from django.conf import settings
from django.core import serializers
from django.db import connections, migrations
from common.sharding.resharding.table_relationships import get_fk_models
def create_central_test_data(folder_path: Path):
    def forwards_code(apps, schema_editor):
        if not settings.TESTING:
            return
        db_alias = schema_editor.connection.alias
        if db_alias != "default":
            return
        models = get_fk_models(get_central_models=True, get_sharded_models=False)
        for model in reversed(models):
            if not folder_path.joinpath(f"{model._meta.db_table}.json").is_file():
                continue
            model = apps.get_model(model._meta.label)
            print("deleting from", model)
            if hasattr(model, "all_objects"):
                model.all_objects.using(db_alias).hard_delete()
            elif hasattr(model, "objects"):
                model.objects.using(db_alias).delete()
            else:
                model.protected_objects.using(db_alias).delete()
        for model in models:
            if not folder_path.joinpath(f"{model._meta.db_table}.json").is_file():
                continue
            model = apps.get_model(model._meta.label)
            print("writing to", model)
            # drop trigger so we can insert IDs with the same value as stored
            drop_trigger_sql = f"DROP TRIGGER IF EXISTS public_{model._meta.db_table}_insert ON {model._meta.db_table};"
            with connections["default"].cursor() as cursor:
                cursor.execute(drop_trigger_sql)
            with open(folder_path.joinpath(f"{model._meta.db_table}.json"), "r") as f:
                serialized = f.read()
            deserialized = serializers.deserialize(
                "json", serialized, ignorenonexistent=True
            )
            if model._meta.db_table == "collector_userprofile":
                print(
                    f"There current {model._meta.db_table} count is {model.objects.count()}"
                )
                print([f.name for f in model._meta.get_fields()])
                print(serialized)
            if hasattr(model, "objects"):
                model.objects.using(db_alias).bulk_create(
                    d.object for d in deserialized
                )
            else:
                model.protected_objects.using(db_alias).bulk_create(
                    d.object for d in deserialized
                )
            set_id_sequence_sql = f"""
            SELECT setval(
                'public.{model._meta.db_table}_id_seq',
                COALESCE((SELECT MAX(id)+1 FROM public.{model._meta.db_table}), 1), 
                false
            );
            """
            with connections["default"].cursor() as cursor:
                cursor.execute(set_id_sequence_sql)
    return {"code": forwards_code, "reverse_code": migrations.RunPython.noop}