Override Django Form Choices

# models.py
class Employee(models.Model):
    INTERNSHIP, FULL_TIME, PART_TIME = range(3)
    EMPLOYEE_STATUS_CHOICES = (
        (INTERNSHIP, 'Internship'),
        (FULL_TIME, 'Full time'),
        (PART_TIME, 'Part time'),
    )

    name = models.CharField(max_length=120)
    status = models.SmallIntegerField(choices=EMPLOYEE_STATUS_CHOICES, default=PART_TIME)

    def __unicode__(self):
        return self.name

# forms.py
class EmployeeForm(forms.ModelForm):
    class Meta:
        model = Employee

From model and form definition above “status” field will rendered as html dropdown with those 3 option. In some case I have to make it as read only form in my HTML, for instance only display “Part time” choices in the html dropdown. We can override it in form like code below.

def __init__(self, *args, **kwargs):
    super(EmployeeForm, self).__init__(*args, **kwargs)
    # 2 is choices index in model definition above.
    self.fields['status'].choices = [EMPLOYEE_STATUS_CHOICES[2]]

For another case, we have to filter by the value in database, for instance.

$ Employee.objects.create(name="John Doe", status=Employee.INTERNSHIP)

In our views.

# Supposed to be get by `id` or `uuid`
instance = Employee.objects.get(name="John Doe")
form = EmployeeForm(request.POST or None, instance=instance)

We have to set the dropdown following the data in database without display the other choices.

def __init__(self, *args, **kwargs):
    super(EmployeeForm, self).__init__(*args, **kwargs)
    
    if self.instance is None:
        self.fields['status'].choices = [EMPLOYEE_STATUS_CHOICES[2]]
    else:
        status = self.instance.status
        self.fields['status'].choices = [(status, dict(EMPLOYEE_STATUS_CHOICES).get(status))]

Set Specific Error in Django Form Clean

Sometimes we have to create our own validation by overriding some method in Form class in Django. there are a lot of way to do this, you can refer to this documentation.

But if we override clean method. We have to set to which field error message supposed to be displayed.

Here is example model and my form if I need to set specific error to manufacture field.

# models.py
class Car(models.Model):
    serie = models.CharField(max_length=120)
    manufacture = models.CharField(max_length=120)

    def __unicode__(self):
        return self.serie


# forms.py
class CarForm(forms.ModelForm):
    
    def clean(self):
        cleaned_data = super(CarForm, self).clean()
        if cleaned_data.get('manufacture') == 'HONDA':
            self.add_error('manufacture', 'Choose another manufacture.')
        return cleaned_data

    class Meta:
        model = Car

With add_error we can set to whatever field we want. If we raise error with forms.ValidationError, error message will displayed globally.

Reference: https://docs.djangoproject.com/en/1.8/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other