Code


Version 2 (modified by mattimustang@…, 8 years ago) (diff)

added screenshots

An AJAX ComboBox Widget for Django

This widget was written to replace the standard <select> form element Django uses for releated objects in forms. The widget is an extended version of the Dojo ComboBox widget. I use this mostly in places where the number of objects in the related objects table exceeds 20 or more. The widget is extracted from a project where some tables have more than 30,000 objects and using a <select> dropdown simply did not scale. This widget supports live lookup of related fields based on a single field in that model and autocompletion.

Credit goes to Eric Moritz's original implementation for inspiring this one.

Screenshots

The selection available after clicking the down arrow:

http://code.djangoproject.com/attachment/wiki/AJAXWidgetComboBox/shot1.png

Selections filtered after typing the letter b:

http://code.djangoproject.com/attachment/wiki/AJAXWidgetComboBox/shot2.png

Requirements

  • Dojo - used for AJAX and widget implementation. I have been using the latest SVN trunk in my project but version 0.2.2 may work for you.
  • The widget tarball attached to this page.

Installation

  • Install simplejson and make sure it is somewhere in your PYTHONPATH.
  • Install Dojo in media/js.
  • Untar nongcombobox-0.9.tar.gz and copy the nong directory into the same directory as dojo and copy the contents of views.py into one of your own views.py files.

My project layout is as follows:

myproject/
    myapp/
        models.py
        views.py
        urls.py
        templates/
            myapp/
                mymodel_form.html
    media/
        js/
            dojo/
            nong/

Example Use

In this example we will create a form that uses the ComboBox widget to for the Article's reporter field in below.

Model

Using the example model from Model Examples:

from django.db import models

class Reporter(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    email = models.EmailField()

    def __str__(self):
        return "%s %s" % (self.first_name, self.last_name)

class Article(models.Model):
    headline = models.CharField(maxlength=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter)

    def __str__(self):
        return self.headline

URLconf

Add a url to the applications urls.py for the reporter_lookup view like so:

from myproject.myapp.models import Reporter

reporter_lookup = {
        'queryset': Reporter.objects.all(),
        'field': 'first_name', # this is the field which is searched
        #'limit': 10, # default is to limit query to 10 results. Increase this if you like.
        #'login_required': False, # default is to allow anonymous queries. Set to True if you want authenticated access.
}

urlpatterns = pattern('',
        (r'^reporter_lookup/$', 'myproject.myapp.views.json_lookup', reporter_lookup),
)

Now test your new reporter_lookup view my going to http://localhost:8000/reporter_lookup/?search=, which should return something like:

[["Reporter 1", 1], ["Reporter 2", 2], ...]

Template

Normally you would have just put {{ form.reporter }} somewhere in your article_form.html template to get a drop down menu with a list of the Reporters. To use the new widget in your form you'll need to add the following to your template:

...
{% block extrahead %}
{% comment %}
load dojo and the combobox widget
{% endcomment %}
<script type="text/javascript" src="/media/js/dojo/dojo.js"></script>
<script type="text/javascript">
    dojo.require("dojo.widget.*");
    dojo.setModulePrefix("nong.widget","../nong/widget");
    dojo.widget.manager.registerWidgetPackage("nong.widget");
    dojo.require("nong.widget.NongComboBox");
</script>

{% comment %}
Add the following if you use the same template for both adding and changing. This block pre-populates the new combobox widget with the current value of the article's reporter.
{% endcomment %}
{% if object %}
<script type="text/javascript">
    function fillComboBoxes() {
        var reporter = dojo.widget.byId("{{ form.reporter.get_id }}");
        reporter.setValue("{{ object.reporter.name }}");
        reporter.setSelectedValue("{{ object.reporter.id }}");
    };
    dojo.addOnLoad(fillComboBoxes);
</script>
{% endif %}

{% endblock %}
...
{% comment %}
Replace the usual {{ form.reporter }} element in the template with this:
{% endcomment %}
<select dojoType="NongComboBox" id="{{ form.reporter.get_id }}" name="reporter" dataUrl="/reporter_lookup/?search=%{searchString}" mode="remote" value="default"></select>
...

That's it! You should be able to submit this form just like you normally would.

Attachments (6)

Download all attachments as: .zip