I am trying to dynamically build a list of admin actions using the get_actions()
method on a ModelAdmin
. Each action relates to a particular instance of another model, and as new instances may be added or removed, I want to make sure the list of actions reflects that.
Here's the ModelAdmin
:
class PackageAdmin(admin.ModelAdmin):
list_display = ('name', 'quality')
def _actions(self, request):
for q in models.Quality.objects.all():
action = lambda modeladmin, req, qset: qset.update(quality=q)
name = "mark_%s" % (q,)
yield (name, (action, name, "Mark selected as %s quality" % (q,)))
def get_actions(self, request):
return dict(action for action in self._actions(request))
(The weird repetitive dict of tuples return value is explained by the Django docs for get_actions()
.)
As expected, this results in a list of appropriat开发者_StackOverflow中文版ely named admin actions for bulk assignment of Quality
foreign keys to Package
objects.
The problem is that whichever action I choose, the same Quality
object gets assigned to the selected Package
s.
I assume that the closures I am creating with the lambda
keyword all contain a reference to the same q
object, so every iteration changes the value of q
for every function.
Can I break this reference, allowing me to still use a list of closures containing different values of q
?
Edit: I realise that lambda
is not necessary in this example. Instead of:
action = lambda modeladmin, req, qset: qset.update(quality=q)
I could simply use def
:
def action(modeladmin, req, qset):
return qset.update(quality=q)
try
def make_action(quality):
return lambda modeladmin, req, qset: qset.update(quality=quality)
for q in models.Quality.objects.all():
action = make_action(q)
name = "mark_%s" % (q,)
yield (name, (action, name, "Mark selected as %s quality" % (q,)))
if that doesn't work, i suspect the bug has something to do with your use of yield
. maybe try:
def make_action(quality):
name = 'mark_%s' % quality
action = lambda modeladmin, req, qset: qset.update(quality=quality)
return (name, (action, name, "Mark selected as %s quality" % quality))
def get_actions(self, request):
return dict([make_action for q in models.Quality.objects.all()])
As I mentioned in my comment to andylei's answer, I just found a solution; using another function to create the closure seems to break the reference, meaning that now every action refers to the correct instance of Quality
.
def create_action(quality):
fun = lambda modeladmin, request, queryset: queryset.update(quality=quality)
name = "mark_%s" % (quality,)
return (name, (fun, name, "Mark selected as %s quality" % (quality,)))
class PackageAdmin(admin.ModelAdmin):
list_display = ('name', 'quality')
def get_actions(self, request):
return dict(create_action(q) for q in models.Quality.objects.all())
I am surprised that q stays the same object within the loop.
Does it work with quality=q.id
in your lambda?
精彩评论