Question

I'm using Django 1.4 with Python 2.7 on Ubuntu 12.10.

I have a form where I need to populate a few drop-downs dynamically (using jQuery) but need 2 of them to be required and the 3rd to be optional.

I'm using Tastypie to help with the API to get the options. Basically the first drop-down is populated with industry level codes for schools. Once a code is selected a category drop-down is populated for all categories for that code. Once the category is chosen a subcategory drop-down is populated for all subcategories for that combination of code and category.

I'm able to require the code drop-down (it's not dynamically populated). However, I'm having a tough time getting the category drop-down to be required. There are basically 2 routes I can take - front-end validation or back-end validation. I'm trying to go with back-end validation so I can easily create further validation if needed.

Here is the form:

class SchoolProductForm(forms.ModelForm):
    cip_category = forms.ChoiceField(required=True,
                                     choices=(('', '----------'),))

    def __init__(self, *args, **kwargs):
        super(SchoolProductForm, self).__init__(*args, **kwargs)

        self.fields['short_description'].widget = TA_WIDGET
        self.fields['salary_info'].widget = TA_WIDGET
        self.fields['job_opportunities'].widget = TA_WIDGET
        self.fields['related_careers'].widget = TA_WIDGET
        self.fields['meta_keywords'].widget = TI_WIDGET
        self.fields['meta_description'].widget = TI_WIDGET
        self.fields['cip'].queryset = models.CIP.objects.filter(
            parent_id__isnull=True)


    class Meta:
        model = models.SchoolProduct
        exclude = ('campus',)

I've tried to override the clean method. I've tried to create a field specific clean method. Neither seem to work.

Variations of the following:

def clean(self):
    super(SchoolProductForm, self).clean()
    if cip_category in self._errors:
        del self._errors['cip_category']
    if self.cleaned_data['cip_category'] == '----------':
        self._errors['cip_category'] = 'This field is required.'

    return self.cleaned_data

This gives an error that there is no cip_category in cleaned_data, which makes sense because it didn't validate.

I've tried variations with the field specific clean:

def clean_cip_category(self):
    data = self.cleaned_data['cip_category']
    self.fields['cip_category'].choices = data

    return data

But get a validation error on the page stating my choice is not one of the available choices.

I've tried to create a dynamic field type (several variations):

class DynamicChoiceField(forms.ChoiceField):
    def valid_value(self, value):
        return True

class SchoolProductForm(forms.ModelForm):
    cip_category = DynamicChoiceField(required=True,
                                      choices=(('', '----------'),))

But it accepts ---------- as a valid option (which I don't want) and causes an error since the ORM tries to match a value of ---------- in the database (which it won't find).

Any ideas?

Was it helpful?

Solution

I was able to solve this with a little overriding of a method in ChoiceField.

I added the field to the form and handled the pre-population with the self.initial:

class SchoolProductForm(forms.ModelForm):
    cip_category = common_forms.DynamicChoiceField(
        required=True, choices=(('', '----------'),))

    def __init__(self, *args, **kwargs):
        super(SchoolProductForm, self).__init__(*args, **kwargs)

        self.fields['short_description'].widget = TA_WIDGET
        self.fields['salary_info'].widget = TA_WIDGET
        self.fields['job_opportunities'].widget = TA_WIDGET
        self.fields['related_careers'].widget = TA_WIDGET
        self.fields['meta_keywords'].widget = TI_WIDGET
        self.fields['meta_description'].widget = TI_WIDGET
        self.fields['cip'].queryset = models.CIP.objects.filter(
            parent_id__isnull=True)

        # Get the top parent and pre-populate
        if 'cip' in self.initial:
            self.initial['cip'] = models.CIP.objects.get(
                pk=self.initial['cip']).top_parent()

    class Meta:
        model = models.SchoolProduct
        exclude = ('campus',)

Where DynamicChoiceField is:

class DynamicChoiceField(forms.ChoiceField):
    def valid_value(self, value):
        return True

Then, in the view I added handling in the form_valid override:

def form_valid(self, form):
    self.object = form.save(commit=False)

    # Handle the CIP code
    self.object.cip_id = self.request.POST.get('cip_subcategory')
    if self.object.cip_id == '':
        self.object.cip_id = self.request.POST.get('cip_category')

    self.object.save()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top