views:

3258

answers:

5

I'm using Django 1.0.2. I've written a ModelForm backed by a Model. This model has a ForeignKey where blank=False. When Django generates HTML for this form it creates a select box with one option for each row in the table referenced by the ForeignKey. It also creates an option at the top of the list that has no value and displays as a series of dashes:

<option value="">---------</option>

What I'd like to know is:

  1. What is the cleanest way to remove this auto-generated option from the select box?
  2. What is the cleanest way to customize it so that it shows as:

    <option value="">Select Item</option>
    

In searching for a solution I came across Django ticket 4653 which gave me the impression that others had the same question and that the default behavior of Django may have been modified. This ticket is over a year old so I was hoping there might be a cleaner way to accomplish these things.

Thanks for any help,

Jeff

Edit: I've configured the ForeignKey field as such:

verb = models.ForeignKey(Verb, blank=False, default=get_default_verb)

This does set the default so that it's no longer the empty/dashes option but unfortunately it doesn't seem to resolve either of my questions. That is, the empty/dashes option still appears in the list.

+3  A: 

from the docs

The blank choice will not be included if the model field has blank=False and an explicit default value (the default value will be initially selected instead).

so set the default and you're ok

zalew
See Edit in question. Thanks for your help.
jlpp
A: 

If all else fails there's always JQuery*.

It's dirty but it works.

(*choose your JS poison)

Edit - I'd like to see some justifications for the downvote - it's arguable that a JS solution is cleaner than the accepted answer and it's certainly quicker to implement. I'd say that this is a reasonable case for 'progressive enhancement'.

andybak
It's messy because the back-end doesn't know about it and now you have to guard against something that's not obvious from the rest of the code.
Tom
+5  A: 

Haven't tested this, but based on reading Django's code here and here I believe it should work:

class ThingForm(models.ModelForm):
  class Meta:
    model = Thing

  def __init__(self, *args, **kwargs):
    super(ThingForm, self).__init__(*args, **kwargs)
    self.fields['verb'].empty_label = None

EDIT: This is documented, though you wouldn't necessarily know to look for ModelChoiceField if you're working with an auto-generated ModelForm.

EDIT: As jlpp notes in his answer, this isn't complete - you have to re-assign the choices to the widgets after changing the empty_label attribute. Since that's a bit hacky, the other option that might be easier to understand is just overriding the entire ModelChoiceField:

class ThingForm(models.ModelForm):
  verb = ModelChoiceField(Verb.objects.all(), empty_label=None)

  class Meta:
    model = Thing
Carl Meyer
Thanks for the help Carl. Your example almost worked but I had to refresh the widget choice list as the last step. See my answer. And Wow; fast turnaround on the bug report!
jlpp
Sidebar: do you know how your last approach would fit in with using Meta: fields to choose which model fields to include on the form?
jlpp
I'm not sure what would happen if you define a form field manually but don't include it in the Meta list of model fields. I'm sure it would appear, but it would it be saved on the model? My guess is yes - try it and see.
Carl Meyer
+1  A: 

With Carl's answer as a guide and after rooting around the Django source for a couple hours I think this is the complete solution:

  1. To remove the empty option (extending Carl's example):

    class ThingForm(models.ModelForm):
      class Meta:
        model = Thing
    
    
      def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = None
        # following line needed to refresh widget copy of choice list
        self.fields['verb'].widget.choices =
          self.fields['verb'].choices
    
  2. To customize the empty option label is essentially the same:

    class ThingForm(models.ModelForm):
      class Meta:
        model = Thing
    
    
      def __init__(self, *args, **kwargs):
        super(ThingForm, self).__init__(*args, **kwargs)
        self.fields['verb'].empty_label = "Select a Verb"
        # following line needed to refresh widget copy of choice list
        self.fields['verb'].widget.choices =
          self.fields['verb'].choices
    

I think this approach applies to all scenarios where ModelChoiceFields are rendered as HTML but I'm not positive. I found that when these fields are initialized, their choices are passed to the Select widget (see django.forms.fields.ChoiceField._set_choices). Setting the empty_label after initialization does not refresh the Select widget's list of choices. I'm not familiar enough with Django to know if this should be considered a bug.

jlpp
Probably not a bug; I guess there's a reason the empty_label argument is documented, but not re-setting of the attribute. Since this is kind of hacky the better option might be to just override the entire form field; see my edited answer.
Carl Meyer
This is odd, but I just tested this on my application and I did not need to reset the fields to get the empty label to be removed...
Paolo Bergantino
Thanks a bunch, this worked perfectly!
schmilblick
+1  A: 

See here for the complete debate on and methods for resolution of this issue.

Thomas B. Higgins