loading...

How to Create an Other field in HTML Select using Django

daveson217 profile image David Babalola ・4 min read

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

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

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

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.

{% 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.

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.

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.

Discussion

pic
Editor guide
Collapse
nyamador profile image
Desmond

Nice one !

Collapse
daveson217 profile image