craigds/django-typed-models

Subclass form validation raises FieldDoesNotExist

ppo opened this issue · 3 comments

ppo commented
class Parent(TypedModel):
    a = models.CharField(...)

class Child1(Parent):
    b = models.OneToOneField(...)

class Child2(Parent):
    pass

Creating a Child2 via the admin, using a model form, raises:
django.core.exceptions.FieldDoesNotExist: Child2 has no field named 'b'

The problem comes from db.models.base.Model._get_unique_checks() that gets _meta.local_fields from parent classes. Parent having all fields from all subclasses, db.models.base.Model._perform_unique_checks() raises that error.

I fixed that problem as follows but maybe someone will have a better solution…

class Parent(TypedModel):
    def _perform_unique_checks(self, unique_checks):
        clean_unique_checks = []
        for model_class, unique_check in unique_checks:
            clean_unique_check = []
            for field_name in unique_check:
                try:
                    clean_unique_check.append(self._meta.get_field(field_name))
                except FieldDoesNotExist:
                    pass
            if len(clean_unique_check):
                clean_unique_checks.append((model_class, clean_unique_check))

        return super()._perform_unique_checks(clean_unique_checks)

Oh right, this happens because OneToOneField has unique=True. For a moment I was stumped trying to figure out where the unique checks were coming from 😄

Another workaround might be to just put that field on the Parent model, since then Child2 will have the field too.

ppo commented

The code above was not correct. unique_check is a tuple of field names, not the field itself.

class Parent(TypedModel):
    def _perform_unique_checks(self, unique_checks):
        # Bug fix: The validation of the default model form raises `FieldDoesNotExist` because Django retrieves fields
        # from parent classes for unique checks... which isn't compatible with the way `TypedModel` works.
        # See https://github.com/craigds/django-typed-models/issues/42
        clean_unique_checks = []
        for model_class, unique_check in unique_checks:
            clean_unique_check = []
            for field_name in unique_check:
                try:
                    self._meta.get_field(field_name)
                    clean_unique_check.append(field_name)
                except FieldDoesNotExist:
                    pass
            if len(clean_unique_check):
                clean_unique_checks.append((model_class, tuple(clean_unique_check)))

        return super()._perform_unique_checks(clean_unique_checks)
ppo commented

@craigds yeah but defining that field in the parent is not clean ;-)