views:

282

answers:

2

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 appropriately 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 Packages.

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)
A: 

I am surprised that q stays the same object within the loop.

Does it work with quality=q.id in your lambda?

Tobu
Thanks for your time, but this doesn't solve the problem. I guess `q.id` in the lambda is still being passed by reference.
Ben James
+2  A: 

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()])
andylei
Thanks, I had just hit upon a solution similar to your second suggestion and was about to post when I read this. +1 for your time.
Ben James
I'm struggling to get this working. Could you post the complete working example for this?
andybak
Ah. sussed it:(edit - no code formatting in comments! bummer...)` def get_actions(self, request): 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)) return dict(make_action(q) for q in models.Quality.objects.all())`
andybak