views:

487

answers:

2

Given the following models (cut down for understanding):

class Venue(models.Model):
    name = models.CharField(unique=True)

class Band(models.Model):
    name = models.CharField(unique=True)

class Event(models.Model):    
    name = models.CharField(max_length=50, unique=True)       
    bands = models.ManyToManyField(Band) 
    venue = models.ForeignKey(Venue)
    start = models.DateField()
    end = models.DateField()

The admin area works great for what I'm doing, but I'd like to open the site up a bit so that certain users can add new Events. For the public portions, I have several "administrative" fields on these models that I don't want the public to see (which is easy enough to fix).

My specific problem, though, is changing the display of the ManyToMany selections when creating a new Event. Because the number of Bands possible to list for an event should not be sent along as a multiselect box, I'd like to use an AutoComplete that handles multiples (like the Tags box, here on StackOverflow!).

I have this part working, and it correctly fills in a hidden input with the Band.id's separated by commas for a value. However, I can't understand how to put together letting Django do the validation using the ModelForms, and somehow also validating the 'Bands' selection.

Ideally, I want to auto-complete like the tags here on StackOverflow, and send along the selected Bands ID's in some kind of Delimited string - all while letting Django validate that the bands passed exist, etc, as if I left the annoying multi-select list in place.

Do I have to create my own Auto-Complete Field type for a form or model, and use that? Is there something else I'm overlooking?

I have seen some existing AutoComplete widgets, but I'd really-really-really like to use my own Autocomplete code, since it's already set up, and some of them look a bit convoluted.

There was a lot more text/explanation here, but I cut back because I'm avoiding Wall Of Text. If I left important stuff out, let me know.

+1  A: 

It's a little hard to say without knowing exactly what your autocomplete code is doing, but as long as it is sending the ids of the bands like they would be sent with the <select>, the ModelForm should validate them as usual.

Basically, your POST string should look like:

name=FooBar2009&bands=1&bands=3&bands=4&venue=7&start=...

The easiest way to do this might be to use Javascript to add (and remove) a hidden input field for each band entered with the name band and the id of the band as the value. Then, when the user submits the form, the browser will take care of posting the right stuff, and the ModelForm will validate it.

tghw
+1 -- that's how I'd do it. For bonus points, replace the multi-select list with the autocomplete widget on the client-side, so the form works with javascript disabled.
elo80ka
Accepted, but it ticks me off that there's not a decent way to do this for users that may not have javascript. Either they get a *huge* select box, or my form has to interact with the user session and ask about one band at a time. Makes me sad, but that's HTTP.
anonymous coward
A: 

Using the annointed jquery autocomplete plugin,

On the client-side I have something like this:

jQuery("#id_tags").autocomplete('/tagging_utils/autocomplete/tasks/task/', {
    max: 10,
    highlight: false,
    multiple: true,
    multipleSeparator: " ",
    scroll: true,
    scrollHeight: 300,
    matchContains: true,
    autoFill: true,
});

So, I have a view that returns when I type in a:

http://skyl.org/tagging_utils/autocomplete/tasks/task/?q=a&amp;limit=10&amp;timestamp=1259652876009

You can see the view that serves that here:

http://github.com/skyl/skyl.org/blob/master/apps/tagging_utils/views.py

Now, it's going to be a little tricky .. you might except the POST, then in the clean method of the field try to .get() based on the strings and raise a form validation error if you can't get it ... right, name = ... unique=True .. so something like (off the top of my head) ... :

def clean_bands(self):
    return Band.objects.filter( name__in = self.cleaned_data['bands'].split(' ') )

You could also check each string and raise a form error if there are no bands by that name .. not sure that the clean method should return a qs. Let me know if this helps and you want me to keep going/clarify.

skyl
what is a clean method for a M2M supposed to return?
skyl