开发者

Django Admin app: building a dynamic list of admin actions

开发者 https://www.devze.com 2022-12-14 03:45 出处:网络
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 adde

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


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?

0

精彩评论

暂无评论...
验证码 换一张
取 消