Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save lucaswxp/70298c6fe49903a84b3a to your computer and use it in GitHub Desktop.
Save lucaswxp/70298c6fe49903a84b3a to your computer and use it in GitHub Desktop.
Check field exists in model django snippet

In your models.py put:

from django.db import models

@classmethod
def model_field_exists(cls, field):
    try:
        cls._meta.get_field(field)
        return True
    except models.FieldDoesNotExist:
        return False

models.Model.field_exists = model_field_exists

Now use it like this:

MyModel.field_exists('some_field')
# > True or False
@florianm
Copy link

florianm commented Mar 22, 2024

Thanks for this snippet! I'll add another pitfall.

The above snippet tests for the existence of the field in the Django models, which is not necessarily present in the database, dependent on whether migrations have been applied.

Consider the following situation:

  • The code starts out at a theoretical migration 0009 and version 0.1.
  • A data migration (0010) runs ModelA.save().
  • A new field new_field is added to ModelA, adding schema migration 0011. (version 0.2)
  • ModelA.save(), a pre_save hook or a post_save hook, are modified to operate on new_field. (version 0.3)
  • The code is deployed to a server at migration 0009 and version 0.1.

The migrations will fail, because:

  • migrations are run in sequence of stated dependencies on previous migrations on the database at migration 0009
  • migrations are run WITH the code at the latest version (0.3) which references new_field which will not exist up until migration 0011.
  • migration 0010 will fail with django.db.utils.ProgrammingError: column ... does not exist

The resulting errors is confusing:

  • The development database will have stepped through migrations running with the latest code at the time - 0010 will have run without the new references to new_field in the ModelA.save() method. No errors! runserver works and all is as expected.
  • pytest will attempt to migrate the test database, fail at migration 0010 but hide any log messages from the failing migrations.
  • pytest will then fail with django.db.utils.ProgrammingError: column ... does not exist.
  • Any deployment further behind (think: test, staging, production servers) will also trip over the migration.

I can think of two approaches:

  • Re-order migrations (but check which ones are applied to any servers - don't touch applied migrations) - in my example, swap 0010 and 0011.
  • Test for the existence of the field through a raw SQL query.
    select count(column_name) from information_schema.columns where table_name='appname_modelname' and column_name='field_name'; is 0 if column does not exist and 1 if it does.

@florianm
Copy link

Found another trip wire: django-guardian will run a "create anonymous user" method which saves the User model.
If you have added new fields to your custom User model AND operate on them in, say, a pre_save signal, then the django-guardian method will definitely fail.

Approach: Implement your own "create anonymous user" method.
https://django-guardian.readthedocs.io/en/stable/userguide/custom-user-model.html#custom-user-model-anonymous

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment