Opened 9 years ago

Closed 9 years ago

Last modified 9 years ago

#24804 closed Bug (invalid)

META API: get_field method cache issue

Reported by: Aamir Rind Owned by: nobody
Component: Core (Cache system) Version: 1.8
Severity: Normal Keywords: meta, meta api, get_field, FieldDoesNotExist, cache, internal cache
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When Model._meta.get_field raises a django.core.exceptions.FieldDoesNotExist exception for field name, later on if we add the field with that name it stills throws exception because of internal cache issue even the field has been added. Consider below code to reproduce the issue:

from django.db.models import CharField
from django.core.exceptions import FieldDoesNotExist

class AutoCleanField(CharField):
    def __init__(self, depends_on=None, *args, **kwargs):
        self.depends_on = depends_on
        super(AutoCleanField, self).__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super(AutoCleanField, self).deconstruct()
        if self.depends_on:
            kwargs['depends_on'] = self.depends_on
        return name, path, args, kwargs

    def contribute_to_class(self, cls, name, **kwargs):
        super(AutoCleanField, self).contribute_to_class(cls, name, **kwargs)
        try:
            field = cls._meta.get_field(self.depends_on)
        except FieldDoesNotExist:
            field = None

        if not field:
            raise TypeError('{0} `depends_on` field "{1}" does not exists in model "{2}".'
                              .format(self.__class__.__name__, self.depends_on, self.model.__name__))

class Test(models.Model):
    auto = AutoCleanField(depends_on='name')  # <--- intentionally setting a field for param `depends_on` which does not exists

After the above code has been run, now add the name field in Test model and re-run, The FieldDoesNotExist exception does not go away:

class Test(models.Model):
    # everything should works fine now, but didn't
    name = models.CharField(max_length=255)
    auto = AutoCleanField(depends_on='name')

Change History (1)

comment:1 by Simon Charette, 9 years ago

Resolution: invalid
Status: newclosed

Hi intellisense,

This behavior might be a bit counter intuitive but it has nothing to do with get_field() caching. It is instead related to how Python builds classes internally.

When a model class is built its defined attributes such as fields are not necessarily ordered the way they are defined; they are collected in an non-ordered mapping (dict) and thus the declared ordering is lost.

In your example you're assuming the auto field will be added after the name one because the latter is declared before. What's happening here is that auto is ordered before name in attrs and thus its contribute_to_class is called before.

You shouldn't assume that dict will be ordered in any way. For example, if you change the seed used to define a an order over unordered collection (python -R) you should be able to make the following example print either an empty tuple or a singleton containing the name field:

from django.db import models

class Contributer(object):
    def contribute_to_class(self, cls, name, **kwargs):
        print(cls._meta.fields)

class Foo(models.Model):
    name = models.CharField()
    auto = Contributer()

I suggest you take a look at how the GenericForeignKey deals with a similar pattern.

Last edited 9 years ago by Simon Charette (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top