How to place inline formsets form fields manually in Django templates and I want to show only specific fields in Template forms
We need to place form fields in specific x,y location and field size as per the layout provided in the attached image manually in a bootstrap template page, below are our models.py & forms.py code
models.py:
from django.db import models
from django.utils import timezone
from datetime import datetime
def generateNewEmpID():
today = datetime.now()
YearMonthDay = today.strftime("%Y%m%d")
newEmpIdPrefix = 'NT' + YearMonthDay
try:
last_employee_id = Employee.objects.all().last().employee_id # Eg : NT2018012710001
except AttributeError:
last_employee_id = None
if not last_employee_id:
newEmpID = str(newEmpIdPrefix) + '10001'
return newEmpID
else:
if last_employee_id[:10] == newEmpIdPrefix:
oldEmpID_int = last_employee_id[11:]
newEmpIDSuffix = int(oldEmpID_int) + 1
newEmpID = newEmpIdPrefix + str(newEmpIDSuffix).zfill(5)
return newEmpID
else:
newEmpID = str(newEmpIdPrefix) + '10001'
return newEmpID
class Employee(models.Model):
employee_id = models.IntegerField(max_length=30, default=generateNewEmpID, primary_key=True)
first_name = models.CharField(max_length=30, blank=False, null=False)
last_name = models.CharField(max_length=30, blank=False, null=False)
account = models.CharField(max_length=100, blank=False, null=False)
designation = models.CharField(max_length=30, blank=False, null=False)
mail_id = models.CharField(max_length=100, blank=False, null=False)
photo = models.ImageField(blank=True, upload_to='employee/photos')
mobile_no = models.IntegerField(max_length=10)
phone_no = models.CharField(max_length=100, blank=True, null=True)
address = models.CharField(max_length=100)
date_of_join = models.DateField(default=datetime.date())
ctc_pa = models.IntegerField(max_length=10)
pan_no = models.CharField(max_length=10)
epfno = models.CharField(max_length=20)
bank = models.CharField(max_length=100)
bank_account = models.CharField(max_length=30)
bank_ifsc = models.CharField(max_length=15)
emergency_contact = models.CharField(max_length=100)
created_date = models.DateTimeField(default=timezone.now)
class Meta:
managed = False
db_table = 'employee'
class dependents(models.Model):
employee_id = models.ForeignKey(Employee, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
relationship = models.CharField(max_length=100)
age = models.IntegerField(max_length=2)
class Meta:
managed = False
db_table = 'emp_dependents'
class education(models.Model):
employee_id = models.ForeignKey(Employee, on_delete=models.CASCADE)
course = models.CharField(max_length=100)
branch = models.CharField(max_length=100)
college = models.CharField(max_length=100)
location = models.CharField(max_length=100)
completion_date = models.DateField()
percentage = models.IntegerField(max_length=3)
class Meta:
managed = False
db_table = 'emp_education'
...................
...................
...................
forms.py
from django import forms
from .models import Employee, Education, Dependents
from django.forms import inlineformset_factory, ModelForm
class EmployeeForm(ModelForm):
class Meta:
model = Employee
class DependentsForm(ModelForm):
class Meta:
model = Dependents
class EducationForm(ModelForm):
class Meta:
model = Education
EmployeeDependentsFormSet = inlineformset_factory(Employee, Dependents, form=EducationForm, extra=1, can_delete=True, can_delete_extra=True)
EmployeeEducationFormSet = inlineformset_factory(Employee, Education, form=EducationForm, extra=1, can_delete=True, can_delete_extra=True)
We need to show only few columns on the template forms in newemp.html (bootstrap) as show in the below image.
To achieve above form layout we are supposed to place fields manually only in a bootstrap template.
How can we create a similar data form in django template using bootstrap.
tried with basic form with single model, but not able to do with parent/child(s) relationship
Fields that are not in the form need to be optional
You have to make sure the fields that you're not displaying in the form are optional in the model. If you don't do this, validation always fails. Please set blank=True null=True
on them, and then run makemigrations
followed by migrations
. This includes the following fields on the Employee
model: "mail_id", "mobile_no", "ctc_pa", "pan_no", "epfno", "bank", "bank_account", and "bank_ifsc".
Side notes on the model code
- Use camel case and singular for model names. This is why I renamed the models
education
anddependents
. See this. - Don't use
max_length
kwarg withmodels.IntegerField
. This is simply ignored. Found this in the following fields:Employee.ctc_pa
,Employee.mobile_no
,Dependent.age
,Education.percentage
. - If you want the date fields to be set to now, you need to use
auto_now_add
(set when creating the instance), not thedefault
kwarg. See this antipattern for more details.
The form code
I added the formsets as properties of the EmployeeForm
so we can encapsulate initialization, validation, and persisting to the database in one class. Like this, the view code is very simple as we can treat the EmployeeForm
like any other form.
# forms.py
class EmployeeForm(forms.ModelForm):
class Meta:
model = Employee
fields = ['first_name', 'last_name', 'account', 'designation',
'address', 'emergency_contact', 'photo']
widgets = {
'first_name': TextInput(attrs={'class': 'form-control'}),
'last_name': TextInput(attrs={'class': 'form-control'}),
'account': TextInput(attrs={'class': 'form-control'}),
'designation': TextInput(attrs={'class': 'form-control'}),
'address': TextInput(attrs={'class': 'form-control'}),
'emergency_contact': TextInput(attrs={'class': 'form-control'}),
'photo': Textarea(attrs={'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Initialize the formsets
self.dependent_formset = DependentFormSet(
data=kwargs.get('data'), files=kwargs.get('files'),
instance=kwargs.get('instance'),
)
self.education_formset = EducationFormSet(
data=kwargs.get('data'), files=kwargs.get('files'),
instance=kwargs.get('instance'),
)
def is_valid(self):
# Check the validity of formsets when you validate this form
is_valid = super().is_valid()
is_valid &= self.dependent_formset.is_valid()
is_valid &= self.education_formset.is_valid()
return is_valid
def save(self, **kwargs):
# After saving the base instance, also save the formsets
saved_employee_instance = super().save(**kwargs)
# Update the instance before saving. The initial instance have no PK.
self.dependent_formset.instance = saved_employee_instance
self.dependent_formset.save()
self.education_formset.instance = saved_employee_instance
self.education_formset.save()
class DependentForm(forms.ModelForm):
class Meta:
model = Dependent
fields = '__all__'
widgets = {
'name': TextInput(attrs={'class': 'form-control'}),
'relationship': TextInput(attrs={'class': 'form-control'}),
'age': NumberInput(attrs={'class': 'form-control'}),
'contact_number': TextInput(attrs={'class': 'form-control'}),
}
class EducationForm(forms.ModelForm):
class Meta:
model = Education
fields = '__all__'
widgets = {
'course': TextInput(attrs={'class': 'form-control'}),
'branch': TextInput(attrs={'class': 'form-control'}),
'college': TextInput(attrs={'class': 'form-control'}),
'location': TextInput(attrs={'class': 'form-control'}),
'completion_date': DateInput(attrs={'class': 'form-control',
'type': 'date'}),
'percentage': NumberInput(attrs={'class': 'form-control'}),
}
DependentFormSet = inlineformset_factory(
Employee, Dependent, form=DependentForm
)
EducationFormSet = inlineformset_factory(
Employee, Education, form=EducationForm
)
View code
Assuming you need the code for create purposes. For update purposes, it's just a matter of creating another subclass of UpdateView
.
# views.py
class EmployeeCreateView(CreateView):
model = Employee
form_class = EmployeeForm
template_name = 'employee_form.html'
def get_success_url(self):
return reverse('success')
HTML Template code
I used Bootstrap grid to create the layout you proposed in the question. For the formsets, I used a table. Although this gives you a lot of room for customization, consider using django-crispy-forms
package. It can reduce the amount of code in the template to a couple of lines which makes if more maintainable and less prone to errors.
{# employee_form.html #}
<head>...</head>
<body>
<form method="post">
{% csrf_token %}
<div class="container my-5">
{{ form.non_field_errors.as_p }}
<div class="row">
<div class="col-8">
<div class="row">
<div class="col">
<div class="row">
<div class="col-3">
{{ form.first_name.label_tag }}
</div>
<div class="col">
{{ form.first_name }} {{ form.first_name.errors }}
</div>
</div>
</div>
<div class="col">
<div class="row">
<div class="col-3">
{{ form.last_name.label_tag }}
</div>
<div class="col">
{{ form.last_name }} {{ form.last_name.errors }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="row">
<div class="col-3">
{{ form.account.label_tag }}
</div>
<div class="col">
{{ form.account }} {{ form.account.errors }}
</div>
</div>
</div>
<div class="col">
<div class="row">
<div class="col-3">
{{ form.designation.label_tag }}
</div>
<div class="col">
{{ form.designation }} {{ form.designation.errors }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-2">
{{ form.address.label_tag }}
</div>
<div class="col">
{{ form.address }} {{ form.address.errors }}
</div>
</div>
<div class="row">
<div class="col-2">
{{ form.emergency_contact.label_tag }}
</div>
<div class="col">
{{ form.emergency_contact }} {{ form.emergency_contact.errors }}
</div>
</div>
</div>
<div class="col">
{{ form.photo }} {{ form.photo.errors }}
</div>
</div>
<div class="row">
<h2>Dependents</h2>
{{ form.dependent_formset.non_field_errors }}
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Relationship</th>
<th>Age</th>
<th>Contact No.</th>
<th></th> <!-- For edit button -->
<th></th> <!-- For delete button -->
</tr>
</thead>
{{ form.dependent_formset.management_form }}
<tbody id="dependent-formset">
{% for dependent_form in form.dependent_formset %}
<tr>
<td>{{ dependent_form.name }} {{ dependent_form.name.errors }}</td>
<td>{{ dependent_form.relationship }} {{ dependent_form.relationship.errors }}</td>
<td>{{ dependent_form.age }} {{ dependent_form.age.errors }}</td>
<td>{{ dependent_form.contact_number }} {{ dependent_form.contact_number.errors }}</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the edit view -->
<a class="btn btn-sm btn-secondary" href="#">Edit</a>
</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the delete view -->
<a class="btn btn-sm btn-danger" href="#">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button class="btn btn-outline-secondary add-dependent-button" type="button">Add dependent</button>
{% with dependent_empty_form=form.dependent_formset.empty_form %}
<table class="d-none">
<tbody id="dependent-empty-form">
<tr>
<td>{{ dependent_empty_form.name }} {{ dependent_empty_form.name.errors }}</td>
<td>{{ dependent_empty_form.relationship }} {{ dependent_empty_form.relationship.errors }}</td>
<td>{{ dependent_empty_form.age }} {{ dependent_empty_form.age.errors }}</td>
<td>{{ dependent_empty_form.contact_number }} {{ dependent_empty_form.contact_number.errors }}</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the edit view -->
<a class="btn btn-sm btn-secondary" href="#">Edit</a>
</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the delete view -->
<a class="btn btn-sm btn-danger" href="#">Delete</a>
</td>
</tr>
</tbody>
</table>
{% endwith %}
</div>
<div class="row">
<h2>Education</h2>
{{ form.education_formset.non_field_errors }}
<table class="table">
<thead>
<tr>
<th>Course</th>
<th>Branch</th>
<th>College</th>
<th>Location</th>
<th>Completion Date</th>
<th>Grade / %</th>
<th></th> <!-- For edit button -->
<th></th> <!-- For delete button -->
</tr>
</thead>
{{ form.education_formset.management_form }}
<tbody id="education-formset">
{% for education_form in form.education_formset %}
<tr>
<td>{{ education_form.course }} {{ education_form.course.errors }}</td>
<td>{{ education_form.branch }} {{ education_form.branch.errors }}</td>
<td>{{ education_form.college }} {{ education_form.college.errors }}</td>
<td>{{ education_form.location }} {{ education_form.location.errors }}</td>
<td>{{ education_form.completion_date }} {{ education_form.completion_date.errors }}</td>
<td>{{ education_form.percentage }} {{ education_form.percentage.errors }}</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the edit view -->
<a class="btn btn-sm btn-secondary" href="#">Edit</a>
</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the delete view -->
<a class="btn btn-sm btn-danger" href="#">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button class="btn btn-outline-secondary add-education-button" type="button">Add education</button>
{% with education_empty_form=form.education_formset.empty_form %}
<table class="d-none">
<tbody id="education-empty-form">
<tr>
<td>{{ education_empty_form.course }} {{ education_empty_form.course.errors }}</td>
<td>{{ education_empty_form.branch }} {{ education_empty_form.branch.errors }}</td>
<td>{{ education_empty_form.college }} {{ education_empty_form.college.errors }}</td>
<td>{{ education_empty_form.location }} {{ education_empty_form.location.errors }}</td>
<td>{{ education_empty_form.completion_date }} {{ education_empty_form.completion_date.errors }}</td>
<td>{{ education_empty_form.percentage }} {{ education_empty_form.percentage.errors }}</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the edit view -->
<a class="btn btn-sm btn-secondary" href="#">Edit</a>
</td>
<td>
<!-- TODO: replace "href" attribute with a real URL to the delete view -->
<a class="btn btn-sm btn-danger" href="#">Delete</a>
</td>
</tr>
</tbody>
</table>
{% endwith %}
</div>
<div class="row mt-5">
<div class="col">
<label for="submit"><button class="btn btn-primary">Save</button></label>
<input id="submit" type="submit" value="Save" class="d-none" />
</div>
</div>
</div>
</form>
</body>
<footer>
<!-- We need JQuery for the code below -->
<script src="https://code.jquery.com/jquery-3.6.3.slim.min.js"
integrity="sha256-ZwqZIVdD3iXNyGHbSYdsmWP//UBokj2FHAxKuSBKDSo="
crossorigin="anonymous"></script>
<!-- Script for dynamically adding forms to the formsets -->
<script>
/**
* Adds an empty form to the formset.
* NOTE: id args should be used without the "#" symbol.
*/
function copyForm(formsetPrefix, formsetId, emptyFormId) {
const total_forms = $(`#id_${formsetPrefix}-TOTAL_FORMS`);
const total_forms_value = total_forms.val();
// Use the value of total forms for the ID of the new form
let empty_form_html = $(`#${emptyFormId}`).html();
empty_form_html = empty_form_html.replace(/__prefix__/g, total_forms_value);
$(`#${formsetId}`).append(empty_form_html);
// Increment the value in the management form
total_forms.val(parseInt(total_forms_value) + 1);
}
$('.add-dependent-button').click(function(e) {
copyForm('dependent_set', 'dependent-formset', 'dependent-empty-form');
});
$('.add-education-button').click(function(e) {
copyForm('education_set', 'education-formset', 'education-empty-form');
})
</script>
</footer>
</html>