How to Create an Other field in HTML Select using Django
Tags: django, webdev, backend, javascript
Published: 2020-04-25 17:22:46

So you have a Django project that you are working on, and you are working on a form that has a `Select` element that needs an *Other* field, but you are not too sure of how to do it.

I was recently in the same shoes. After looking up on Google, the only way I found to do it was to create additional fields in my Django model, which I did not really like. I then recalled that in this same project, I had once succeeded in editing the request parameters, so I tried that out. Although that did not really work out (the part I added gets stripped off when the requests is passed to the `views.py`), I still found a way to add the extra parameter to it.

I am assuming you have done the project configurations.
Ok, let's begin.

Let's start by creating our model. Let's use a basic model here

```python
class Person(models.Model):    
    title = models.CharField(max_length=50, blank=True,
        choices= [             
            ('Mr', 'Mr.'), 
            ('Mrs', 'Mrs'), 
            ('Other(Specify)', 'Other(Specify)'),        
        ]        
    )       
    surname = models.CharField(max_length=50)
    firstname = models.CharField(max_length=50)
    middlename = models.CharField(max_length=50, blank=True)          

    class Meta:
        verbose_name = "Person"
        verbose_name_plural = "Persons"

    def __str__(self):
        return self.surname + ", " + self.firstname +" "+ self.middlename 
```
Then, lets move on to create our `forms.py`

```python
class PersonForm(ModelForm):
        
    class Meta:
        model = Person
        fields = [
            'title', 'surname', 'firstname', 'middlename'            
        ]                
        
    def __init__(self, *args, **kwargs):
        super(PersonForm, self).__init__(*args, **kwargs)
```

`urls.py`
```python
from django.urls import re_path
from .views import add_person

urlpatterns = [
    # ... Other urls
    re_path(r'^person/create/$', add_person, name='add_person'),  
]
```

Then lets move on to our HTML template. I am also using `[django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks "django-widget-tweaks")` to render the form fields.
I created a div that contained the textfield for the *Other Specify*, and it has a CSS class, `titlespecify`. The display is initially set to none.
```html
{% load widget_tweaks %}
<form method="post" action="{% url 'add_person' %}"  class="" novalidate>
  {% csrf_token %}  
    {% for field in form %}
      {% if field.id_for_label == "id_title" %}
        <div class="form-group{% if field.errors %} has-error{% endif %}">
            <label for="id_title">Title</label>
            {% render_field form.title class="form-control form-control-sm" %}
            <div class="mt-1 titlespecify" style="display:none">
                <input class="form-control form-control-sm" type='text' name='titlespecify' placeholder="Specify Title" />
            </div>      
            {% for error in form.title.errors %}
                <p class="help-block">{{ error }}</p>
            {% endfor %}
        </div>
      {% else %}
        <div class="form-group{% if field.errors %} has-error{% endif %}">
          <label for="{{ field.id_for_label }}">{{ field.label }}</label>

          {% render_field field class="form-control form-control-sm" %}

          {% for error in field.errors %}
            <p class="help-block">{{ error }}</p>
          {% endfor %}
        </div>   
      {% endif %}    
    {% endfor %}
  <div class="form-group">
    <button type="submit" class="btn btn-primary">Add Person</button>
    <button type="button" class="btn btn-default">Close</button>    
  </div>
</form>
```

Now, we have to add codes in our Javascript file to show the textfield when Other(Specific) is selected, and hide it when another item is selected.
Add the following to your JavaScript file. If you are not using django-widget-tweaks, and you are not sure what class or Id the Select element has, render the form the way you would normally do, and then right click on the `Select` element, and click *Inspect Element.*  You can see the ID/CLASS name of the `Select` element there. We would be hiding or showing if a change event from the `Select` element occurs.
```javascript
jQuery('#id_title').on('change', () => {
    // You can also do this with document.querySelector('')
    if ($('#id_title option:selected').text() == 'Other(Specify)') {
        jQuery('.titlespecify').show();                        
    } else {                        
        jQuery('.title1specify').hide();
    }
}); 
```
When you click the submit button, every field in the form makes up part of the request parameters. 
The `name` and `value` of the element are translated into `key-value` pairs (a dictionary) later on.
The request parameters is of the form,
`csrfmiddlewaretoken=khN5maBATog3JjHiTAPO9IMSLvKAXVZZ4Ah7ZuPIbfHOdUxswV8bz&title=Other(Specify)&titlespecify=Reverend&surname=Doe&firstname=John&middlename=Michael`
You can look at the request in the Network tab of your Developer Console, when you try to Submit (Ctrl+Shift+E for Firefox).

We are almost done. All that is left is our `views.py`.

```python
def add_person(request):
    data = dict()

    if request.method == 'POST':
        form = PersonForm(data=request.POST)
        if form.is_valid():                      
            
            new_person = form.save(commit=False) 

            if new_person.title == "Other(Specify)":
                new_person.title = request.POST.get('titlespecify')                    
            new_person.save()   
                    
            data['form_is_valid'] = True            
        else:
            
            data['form_is_valid'] = False           
    else:
        form = PersonForm()

    context = {'form': form}
    data['html_form'] = render_to_string('person/person_create.html',
        context,
        request=request
    ) 
    return JsonResponse(data)  
```

If you print out `request.POST` on the console, you would find out that what is returned is a QueryDict.
Something like this. You see that the `titlespecify` key still exists.

```
<QueryDict: {
    'csrfmiddlewaretoken': ['khN5maBATog3JjHiTAPO9IMSLvKAXVZZ4Ah7ZuPIbfHOdUxswV8bz'], 
    'title': ['Other(Specify)'],
    'titlespecify': ['Reverend'], 
    'surname': ['Doe'], 
    'firstname': ['John'], 
    'middlename': ['Michael'],              
    }>
```

But after `form = PersonForm(data=request.POST)` is run, `titlespecific` is stripped from it because it is not part of the fields list in the Meta class of PersonForm in `forms.py`. When we print out the `form.cleaned_data`, we get

```
{
    'title': 'Other(Specify)', 
    'surname': 'Doe', 
    'firstname': 'John', 
    'middlename': 'Michael', 
}
```

If the form is valid, we save the object without committing, then replace the Title if what is already in it is *Other(Specify)*.
After that, we then finally save the new Person object into the database.
And we are done, I hope you understood this.
Thanks for reading.