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.
Top comments (2)
Nice one !
Thanks sir