views:

227

answers:

2

I have this model:

class Aircraft(models.Model):
 model  = models.CharField(max_length=64, blank=True)
 type  = models.CharField(max_length=32)
 extra  = models.CharField(max_length=32, blank=True)
 manufacturer = models.CharField(max_length=32)
 engine_type = models.IntegerField("Engine Type", choices=ENGINE_TYPE, default=0)
 cat_class = models.IntegerField("Category/Class", choices=CAT_CLASSES, default=1)

And I have a "find aircraft" page where the user is presented with a form where they can enter data that will be used to find all aircraft that fits their criteria. For instance the user can enter "boeing" into a textbox and "jet" into the engine_type box, and it will display all boeing jets in the database. The way I'm doing this now is by this form:

class AircraftSearch(ModelForm):
 search = forms.CharField(max_length=100, required=False)
 class Meta:
  model = Aircraft
  fields = ('engine_type', 'cat_class', )

And then a (needlessly complex) view which converts the data from this form into a set of filter()'s which get added to Aircraft.objects.all(). (Instead of having 4 seperate search fields for each CharField, I have combined them all into one search field.)

This all works, but with one problem. If the user wants to exclude engine type from their search criteria, then they're screwed because "Any" is not a valid choice for the engine type field. I'm going to have to create a new field/widget for engine type and category/class to include "Any", which kind of defeats the purpose of using a model view in the first place

I'm curious. Is there a better way? This seems like a very common task which has to have been tackled by someone else already, but a google search brings up nothing.

+3  A: 

Functionally, "any" would be achieved by just not including that particular search vector in the filtering.

Generally, ModelForms are for creating and editing models; in this case, I'm not sure it's helping you much more than just doing a regular form:

class AircraftSearch(forms.Form):
    search = forms.CharField(max_length=100, required=False)
    engine_type = forms.ChoiceField(choices=ENGINE_TYPE)
    cat_class = forms.ChoiceField(choices=CAT_CLASS)

To do the search, you then just filter when fields are non-empty:

def search(request):
    if request.method == 'POST':
        results = Aircraft.objects.all()

        search = request.POST.get('search', None)
        if search:
            results = results.filter(Q(model=search)|Q(type=search)|Q(extra=search)|Q(manufacturer=search))

        engine_type = request.POST.get('engine_type', None)
        if engine_type:
            results = results.filter(engine_type=engine_type)

        cat_class = request.POST.get('cat_class', None)
        if cat_class:
            results = results.filter(cat_class=cat_class)

        return render_to_response('aircraft.html', {'form': AircraftSearch(request.POST), 'aircraft': results})

    return render_to_response('aircraft.html', {'form': AircraftSearch()})
tghw
The problem is that 'choices=ENGINE_TYPE' only includes valid choices such as "jet" and "piston", etc, and not "Any". You either need to add that option to the ENGINE_TYPE list, or each SearchForm will include one of the options and you'll be forced to filter by a specific engine type. Its not a problem for me to create a searchform with the "Any" option added to the ENGINE_TYPE list, but it feels hacky to me and if a better "non-hacky" method exists I'd rather do that.
nbv4
Seems to me that doing something like choices=(('', 'Any'),) + ENGINE_TYPE is a pretty clean fix for it. You don't introduce "any" to the model, but it's available to the form.
tghw
A: 

I think you should not use a ModelForm.
Typical ModelForm usescase is data manipulation, and not its search.

Instead, make a completely new form, based on the fields you need and with checkboxes that user will (de-)select to disable searching a particular field.
Of course, you still should use the choices defined in the model, by simply importing the file and using that list.

Roberto Liffredo