Admin list_display Ajax

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/*
Sometimes it can be time consuming to go through a bunch of objects in Django's
Admin if you only need to update one field in each. An example of this is an
`order` field that allows you to manually set the order for a queryset.

This snippet contains examples of how to set up the Admin list_display to
make Ajax calls to a custom view (uses jQuery).

The following code may not be worthy of being a snippet, it's all pretty
straightforward, but here it is for those who just want to copy-and-paste.

Here is an example of the order field mentioned above::

    order = models.IntegerField(
        blank=True, null=True,
        help_text="""This field determines the order that products appear on
        the site. Enter any number, it does not matter what the number is, only
        that it is sequential compared to other products on the site. It is
        recommened that you use large numbers such as 100 and 200 to allow room
        for additional products or easy reordering in the future."""
        )

In your `models.py` add the following `order_helper` method to your
`list_display` [1]_. This will make <input> elements show up::

    def order_helper(self):
        return u'''<input type="text" name="%s" value="%s" size="3"
                class="order-helper">''' % (self.id, self.order)
    order_helper.allow_tags = True

.. [1] http://www.djangoproject.com/documentation/model-api/#list-display

Put the following jQuery somewhere that gets executed. This will color the
input boxes if the change succeeded or failed::
*/

    $(document).ready(function() {
        $('.order-helper').css("margin", "0");
        $('.order-helper').blur(function(){
            var input = $(this);
            $.ajax({
                url: "./"+ input.attr('name') +"/order/",
                data: order=input.attr('value'),
                type: "POST",
                complete: function(xhr_obj, msg){
                    if (msg == 'success') {
                        input.css("border", "1px solid green");
                    } else {
                        input.css("border", "1px solid red");
                    }
                },
            });

        });
    });

/*
Make a custom view somewhere to recieve the Ajax POST::

    @login_required
    @require_POST
    def order_helper(request, prod_id):
        prod = get_object_or_404(Product, id=prod_id)
        prod.order = request.POST.get('order')
        prod.save()
        return HttpResponse(content='Ok', status=200)

In your `urls.py` add a call to your new view. (If you use the same /admin/...
URL scheme as this example does, it must be *above* your regular Admin URLconf
include statement.)::

    url(r'^admin/products/product/(?P<prod_id>\d+)/order/$',
        'path.to.order_helper'),
*/

Comments

andybak (on April 16, 2008):

Nice idea. Doesn't seem to work in newforms-admin as all the tags are getting stripped out of the list display. Might be as a result of this: http://code.djangoproject.com/ticket/6226

I am wondering whether this could be made generic and a lot of the logic moved into the javascript. i.e. when any model property is marked as 'ajax editable' (not sure best way to do this) then the list display for that property is dynamically replaced with an Ajax form field that passes property name, id and new value to a function that updates that property. Could even use existing validation code for the model or model_form?

#

andybak (on April 16, 2008):

Quick update on previous post. The problem with newforms-admin was down to me incorrectly setting my i_am_an_idiot property ;-)

You do however have to use mark_safe() as well as allow_tags. i.e.

from django.utils.safestring import mark_safe ... def order_helper(self): return mark_safe(u'''...etc...etc...''')

Probably a nice idea to set the following two as well:

order_helper.admin_order_field = 'order'
order_helper.short_description = 'Order'

#

andybak (on July 3, 2008):

Should line 43 be something like:

data: "order="+input.attr('value'),

#

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.