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
@simkimsia
Copy link

simkimsia commented May 4, 2023

Question

how do i make this as part of an abstract class such that any concrete Django model class that inherits the abstract will auto have the class method?

Answer

from django.db import models

class FieldExistsModel(models.Model):
    class Meta:
        abstract = True

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

Now, any Django model that inherits from FieldExistsModel will have the field_exists method available:

class MyModel(FieldExistsModel):
    name = models.CharField(max_length=100)
    description = models.TextField()

# Usage
field_exists = MyModel.field_exists('name')  # returns True
field_does_not_exist = MyModel.field_exists('nonexistent_field')  # returns False

By setting abstract = True in the Meta class, you inform Django that this base class should not be used to create any database tables, and it should only be used for inheritance.

@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