Skip to content

millioner/django-ajax-select

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

It is a fork of Chris Sattinger's django-ajax-selects (http://code.google.com/p/django-ajax-selects/)

Enables editing of ForeignKey, ManyToMany and simple text fields using the Autocomplete - jQuery plugin. django-ajax-selects will work in any normal form in the admin as well.

User experience

selecting:

selected:

The user is presented with a text field. They type a search term or a few letters of a name they are looking for, an ajax request is sent to the server, a search channel returns possible results. Results are displayed as a drop down menu. When an item is selected it is added to a display area just below the text field.

A single view services all of the ajax search requests, delegating the searches to named 'channels'.

A channel is a simple class that handles the actual searching, defines how you want to treat the query input (split first name and last name, which fields to search etc.) and returns id and formatted results back to the view which sends it to the browser.

For instance the search channel 'contacts' would search for Contact models. The class would be named ContactLookup. This channel can be used for both AutoCompleteSelect (foreign key, single item) and AutoCompleteSelectMultiple (many-to-many) fields.

Simple search channels can also be automatically generated, you merely specify the model and the field to search against (see examples below).

Custom search channels can be written when you need to do a more complex search, check the user's permissions, format the results differently or customize the sort order of the results.

Tested with Django 1.3

Getting started

In settings.py :

INSTALLED_APPS = (
            ...,
            'ajax_select'
            )

in your settings.py define the channels in use on the site, for example:

AJAX_LOOKUP_CHANNELS = {
    # the simplest case, pass a DICT with the model and field to search against :
    'track' : dict(model='music.track',search_field='title'),
    # this generates a simple channel
    # specifying the model Track in the music app, and searching against the 'title' field

    # or write a custom search channel and specify that using a TUPLE
    'contact' : ('peoplez.lookups', 'ContactLookup'),
    # this specifies to look for the class `ContactLookup` in the `peoplez.lookups` module
}

Custom search channels can be written when you need to do a more complex search, check the user's permissions (if the lookup URL should even be accessible to them, and then to perhaps filter which items they are allowed to see), format the results differently or customize the sort order of the results. Search channel objects should implement the 4 methods shown in the following example.

peoplez/lookups.py

from peoplez.models import Contact
from django.db.models import Q

class ContactLookup(object):

    def get_query(self,q,request):
        """ return a query set.  you also have access to request.user if needed """
        return Contact.objects.filter(Q(name__istartswith=q) | Q(fname__istartswith=q) | Q(lname__istartswith=q) | Q(email__icontains=q))

    def format_result(self,contact):
        """ the search results display in the dropdown menu.  may contain html and multiple-lines. will remove any |  """
        return u"%s %s %s (%s)" % (contact.fname, contact.lname,contact.name,contact.email)

    def format_item(self,contact):
        """ the display of a currently selected object in the area below the search box. html is OK """
        return unicode(contact)

    def get_objects(self,ids):
        """ given a list of ids, return the objects ordered as you would like them on the admin page.
            this is for displaying the currently selected items (in the case of a ManyToMany field)
        """
        return Contact.objects.filter(pk__in=ids).order_by('name','lname')

HTML is fine in the result or item format. Newlines and pipe chars will be removed and everything will be escaped properly.

Include the urls in your site's urls.py. This adds the lookup view and the pop up admin view. (r'^ajax_select/', include('ajax_select.urls')),

##It you want to use django-ajax-select in your admin page,

for an example model:

class ContactMailing(models.Model):
    """ can mail to multiple contacts, has one author """
    contacts = models.ManyToManyField(Contact, blank=True)
    author = models.ForeignKey(Contact, blank=False)
    ...

In the admin.py for this app:

from ajax_select import make_ajax_form

class ContactMailingAdmin(Admin):
    form = make_ajax_form(ContactMailing, {'author': 'contact', contacts='contact'))

make_ajax_form( model, fieldlist) is a factory function which will insert the ajax powered form field inputs so in this example the author field (ForeignKey) uses the 'contact' channel and the contacts field (ManyToMany) also uses the 'contact' channel

If you need to write your own form class then specify that form for the admin as usual:

from forms import ContactMailingForm

class ContactMailingAdmin(admin.ModelAdmin):
    form = ContactMailingForm

admin.site.register(ContactMailing,ContactMailingAdmin)

in forms.py for that app:

from ajax_select.fields import AutoCompleteSelectMultipleField, AutoCompleteSelectField

class ContactMailingForm(models.ModelForm):
    # declare a field and specify the named channel that it uses
    contacts = AutoCompleteSelectMultipleField('contact', required=False)
    author = AutoCompleteSelectField('contact', required=False)

##Using ajax selects in a FormSet

There might be a better way to do this.

forms.py

from django.forms.models import modelformset_factory
from django.forms.models import BaseModelFormSet
from ajax_select.fields import AutoCompleteSelectMultipleField, AutoCompleteSelectField

from models import *

# create a superclass
class BaseTaskFormSet(BaseModelFormSet):

    # that adds the field in, overwriting the previous default field
    def add_fields(self, form, index):
        super(BaseTaskFormSet, self).add_fields(form, index)
        form.fields["project"] = AutoCompleteSelectField('project', required=False)

# pass in the base formset class to the factory
TaskFormSet = modelformset_factory(Task,fields=('name','project','area'),extra=0,formset=BaseTaskFormSet)

##Help text

If you are using AutoCompleteSelectMultiple outside of the admin then pass in show_help_text=True. This is because the admin displays the widget's help text and the widget would also. But when used outside of the admin you need the help text. This is not the case for AutoCompleteSelect.

##License

Dual licensed under the MIT and GPL licenses: http://www.opensource.org/licenses/mit-license.php http://www.gnu.org/licenses/gpl.html

##Changelog

1.2 - Js files was refactored. All static content is collected into 'static' folder. And example project was written.

About

Enables editing of `ForeignKey`, `ManyToMany` and simple text fields using the Autocomplete - `jQuery` plugin.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published