Hi Simon,
Thanks for the pointer, but I don't think that helps.
The fields are already declared using the existing fields / readonly_fields attributes on the ExampleAdmin class - and this is what get_fields / get_readonly_fields return. The system check fails because the fields declared don't exist on the ExampleAdmin class nor on the model. Here's the relevant lines from contrib/admin/checks.py:
def _check_readonly_fields_item(self, cls, model, field_name, label):
if callable(field_name):
return []
elif hasattr(cls, field_name):
return []
elif hasattr(model, field_name):
return []
else:
try:
model._meta.get_field(field_name)
except FieldDoesNotExist:
return [
checks.Error(
"The value of '%s' is not a callable, an attribute of '%s', or an attribute of '%s.%s'." % (
label, cls.__name__, model._meta.app_label, model._meta.object_name
),
hint=None,
obj=cls,
id='admin.E035',
)
]
else:
return []
If the thumbnail fields were defined as methods on the ExampleAdmin, all would be fine e.g.:
class ExampleAdmin(models.ModelAdmin):
fields = ['image', 'image_thumbnail']
def image_thumbnail(self, obj):
return "<img src="%s"/>" % obj.image.url
That's fine, but if there's lots of image fields (with a variety of names) spread over several ModelAdmin classes, then you end up with a lot of duplicated code. The normal solution then is to refactor the code. And that's where I get stuck - I can't see (short of metaclass programming!) how to inject the methods into the class such that the system check succeeds.
I therefore suspect that the check is actually borked, and it should be checking hasattr(instance, field_name) rather than hasattr(cls, field_name)
So I see three possibilities, in order of probability:
- I'm being dumb, and there's an easy way to dynamically create attributes on a ModelAdmin that passes system checks
- The system check is incorrect, and should allow dynamically created attributes on ModelAdmin when validating fields
- Metaclass programming is really the right way to do this
Malcolm