Login

Chaining select boxes in admin with MooTools

Author:
neybar
Posted:
September 5, 2008
Language:
Python
Version:
1.0
Score:
0 (after 0 ratings)

This code will allow you to use chained select boxes in the django automatic admin area. For example, you may have a product, then a category and subcategory. You'd like to create a product, and then choose a category, and then have a chained select box be filled with the appropriate subcategories.

  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
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#This requires Mootools.  
#Get it at http://mootools.net/downloads/mootools-1.2-core-yc.js  
#I put mine in media/js.

#**Here are the models:**

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

    class Meta:
        app_label = 'product'
        verbose_name_plural = 'Categories'

class SubCategory(models.Model):
    category = models.ForeignKey('Category')
    name = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

    class Meta:
        app_label = 'product'
        verbose_name = 'Sub-Category'
        verbose_name_plural = 'Sub-Categories'

STATUS = (
    ( 'A', 'Active' ),
    ( 'I', 'Inactive' ),
    ( 'O', 'Out of Production'),
)

class Product(models.Model):
    name = models.SlugField(max_length=255)
    status = models.CharField(max_length=2, choices=STATUS, default='I')
    subcategory = models.ForeignKey(SubCategory)
    brand = models.CharField(max_length=255, blank=True)
    manufacturer = models.CharField(max_length=255, blank=True)
    description = models.TextField()
    short_description = models.CharField(max_length=255, blank=True)
    part_number = models.CharField(max_length=50, blank=True)
    model_number = models.CharField(max_length=50, blank=True)
    quantity = models.IntegerField(null=True, blank=True)
    image = models.ImageField(upload_to='product_images', blank=True)
    image_thumbnail = models.ImageField(upload_to='product_images', blank=True)
    created = models.DateField(auto_now_add=True)

    def __unicode__(self):
        return self.name.title()

    class Meta:
        app_label = 'product'


#**Then in admin.py:**

from django.contrib import admin
from django import forms
from vacancy.product.models import Category, SubCategory, Product

#first create a custom form to use in admin
class ProductAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ProductAdminForm, self).__init__(*args, **kwargs)
        print dir(self.instance)
        try:
            #Try and set the selected_cat field from instance if it exists
            self.fields['selected_cat'].initial = self.instance.subcategory.category.id
        except:
            pass
    #The product model is defined with out the category, so add one in for display
    category = forms.ModelChoiceField(queryset=Category.objects.all().order_by('name'), widget=forms.Select(attrs={'id':'category'}), required=False)
    #This field is used exclusively for the javascript so that I can select the 
    #correct category when editing an existing product
    selected_cat = forms.CharField(widget=forms.HiddenInput, required=False)

    class Meta:
        model = Product

    class Media:
        #Alter these paths depending on where you put your media 
        js = (
            'js/mootools-1.2-core-yc.js',
            'js/product.js',
        )

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm
    #I don't like using a fieldset here, because it makes the form more brittle,
    #if you change the model for form be sure to update the fieldset.
    #I'm using it in this instance because I need for category to show up 
    #right above the subcategory
    fieldsets = (
        (None, {
            'fields' : ('name','status','category','subcategory','description')
        }),
        ('Optional', {
            'classes' : ('collapse',),
            'fields' : ('brand','manufacturer','short_description','part_number','model_number','image','image_thumbnail','selected_cat')
        })
    )

admin.site.register(Product, ProductAdmin)

#**put this file in your media dir (ex: project_foo/media/js/product.js):**
window.addEvent('domready',function() {

    //You may will need to change category, id_subcategory, and id_selected_cat
    //to match the names of the fields that you are working with.
    var category = $('category');
    var subcategory = $('id_subcategory');

    var update_subcat = function() {
        var cat_id = $('id_selected_cat').value;
        if (cat_id) {
            $('id_selected_cat').value='';
            category.value=cat_id;
        } else {
            cat_id = category.getSelected()[0].value;
        }
        //cat_id = category.getSelected()[0].value;
        var subcat_id = subcategory.getSelected()[0].value;
        var request = new Request.JSON({
            url: "/product/subcategory/"+cat_id+"/",
            onComplete: function(subcats){
                subcategory.empty();
                if (subcats) {
                    subcats.each(function(subcat) {
                        var o = new Element('option', {
                            'value':subcat.pk,
                            'html':subcat.fields.name
                        });
                        if (subcat.pk == subcat_id) {
                            o.set('selected','selected');
                        }
                        o.inject(subcategory);
                    });
                } else {
                    var o = new Element('option', {
                        'value':'',
                        'html':'Please Choose A Category First'
                    });
                    o.inject(subcategory);
                }
            }
        }).get();
    };
    update_subcat();

    category.addEvent('change', function(e){
        e.stop();
        update_subcat();
    });

});

#**Then create the view to handle the ajax subcategory request:**

urls.py:
urlpatterns = patterns('',
    url(r'^subcategory/(?P<category_id>\d*)/$', 'foo.app.views.subcategory', name='subcategory'),
)

views.py:
from django.http import HttpResponse
from django.core import serializers

def subcategory(request, category_id):
    return HttpResponse(serializers.serialize('json', SubCategory.objects.filter(category=category_id), fields=('pk','name')))

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 2 weeks ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 10 months, 1 week ago
  5. Help text hyperlinks by sa2812 11 months ago

Comments

miguelvel18 (on October 12, 2009):

when I change the DEBUG variable to False in settings.py , this snippet doesn't work, Why? Help me

#

Please login first before commenting.