Reputation: 6107
I'd like to dynamically include some extra fields in the list_display
of the admin for one of my models. I plan on overriding get_list_display
to append a string representing a ModelAdmin
method but how can I dynamically create the ModelAdmin
methods?
In the example below if get_list_display
returns [..., 'module_1', 'module_2']
that means I need methods for module_1
and module_2
as they're not model fields.
class Type(models.Model):
...
modules = models.ManyToManyField(Module)
class User(AbstractBaseUser):
...
type = models.ForeignKey(Type)
class UserModuleRecord(User):
class Meta:
proxy=True
@admin.register(UserModuleRecord)
class UserModuleRecordAdmin(admin.ModelAdmin):
list_display = ['id', 'first_name', 'last_name']
def get_queryset(self, request):
return (
super().get_queryset(request)
.annotate_the_additional_list_display_values()
)
def get_list_display(self, request):
list_display = super().get_list_display(request)
modules = Module.objects.all()
for module in modules:
list_display.append('module_%s' % module.id)
return list_display
Additionally is it possible to create something similar to how get_FOO_display
works so there's only one admin method required?
UPDATE
I think I'm close with the following but am getting an error TypeError: 'partialmethod' object is not callable
from functools import partialmethod
def get_list_display(self, request):
list_display = super().get_list_display(request)
modules = Module.objects.all()
for module in modules:
attr_name = 'module_%s' % module.id
list_display.append(attr_name)
if not hasattr(self, attr_name):
setattr(self, attr_name, partialmethod(
self._module_id, field=attr_name
))
return list_display
def _module_id(self, obj, field=''):
return getattr(obj, field, '')
Upvotes: 2
Views: 1857
Reputation: 6107
The following makes use of functools.partial
to return a partial object which behaves like a function when called - similar to Django's get_FOO_display()
.
@admin.register(models.EmployeeTrainingRecord)
class EmployeeTrainingRecordAdmin(admin.ModelAdmin):
ordering = ('email',)
list_display = [
'id',
'first_name',
'last_name',
]
def get_queryset(self, request):
return (
super().get_queryset(request)
.annotate_training_dates()
)
def get_list_display(self, request):
list_display = super().get_list_display(request)
training_modules = models.Training.objects.all()
for training in training_modules:
attr_name = 'training_%s' % training.id
if not hasattr(self, attr_name):
list_display.append(attr_name)
func = partial(self._get_training_id, field=attr_name)
func.short_description = training.name
setattr(self, attr_name, func)
return list_display
@staticmethod
def _get_training_id(obj, field=''):
return getattr(obj, field, '')
Upvotes: 1
Reputation: 278
If I'm understanding your question correctly I think you're half way to the answer already.
As you know, to add anything custom to the list_display
fields that isn't already a model field you need to implement it with a method on your ModelAdmin
instance.
You can dynamically create field methods on the ModelAdmin instance using python's setattr
and a wrapped function and doing it when the instance is initialised.
For example you could create a series of methods on your instance this way:
def make_dynamic_field(*args):
def dynamic_field(self, obj):
i = args[0]
return f'{obj.first_name} {i}'
return dynamic_field
@admin.register(UserModuleRecord)
class UserModuleRecordAdmin(admin.ModelAdmin):
...
list_display = ['id', 'first_name', 'last_name', 'foo_0', 'foo_1']
def __init__(self, model, admin_site):
super().__init__(model, admin_site)
for i in range(2):
setattr(self, f'foo_{i}', make_dynamic_field(i))
I have used the index variable i
as an example but you could potentially inject any variable into your dynamic field method this way.
Upvotes: 0