I just created a django form using modelformset, and now i can add booktitle form dynamically under student form (using js).Now i wants add Dynamic subform under each book form. Is that possible in django,
Model:
# models.py
from django.db import models
class Student(models.Model):
name = models.CharField(max_length=100)
student_id = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
student = models.ForeignKey(Student, related_name='books', on_delete=models.CASCADE)
def __str__(self):
return self.title
Forms:
# forms.py
from django import forms
from django.forms import modelformset_factory
from .models import Student, Book
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ['name', 'student_id']
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ['title']
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1)
Views:
from django.shortcuts import render, redirect
from django.views.generic import ListView, CreateView
from django.forms import modelformset_factory
from .models import Student, Book
from .forms import StudentForm, BookInlineFormSet
# Student List View
class StudentListView(ListView):
model = Student
template_name = 'student_list.html'
context_object_name = 'students'
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.views.generic import CreateView
from django.forms import modelformset_factory
from .models import Student, Book
from .forms import StudentForm, BookForm
from django.http import HttpResponse
class AddStudentView(CreateView):
model = Student
form_class = StudentForm
template_name = 'add_student.html' # Specify your template
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Create a formset for Book instances, related to the student
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1) # Allow adding 1 extra book form
context['book_formset'] = BookInlineFormSet(queryset=Book.objects.none()) # Empty formset for GET request
return context
def form_valid(self, form):
# Save the student object first
student = form.save()
# Get the book formset from the POST data
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1)
book_formset = BookInlineFormSet(self.request.POST)
# If the book formset is valid, save the books and link them to the student
if book_formset.is_valid():
books = book_formset.save(commit=False)
for book in books:
book.student = student # Associate the book with the student
book.save()
# Redirect to another view after successful creation (e.g., student list view)
return redirect('student_list') # Change 'student_list' to your list view URL name
def form_invalid(self, form):
# Handle invalid form case for StudentForm
context = self.get_context_data(form=form)
# Get the formset data from POST, so that invalid BookForm set can be shown
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1)
book_formset = BookInlineFormSet(self.request.POST)
context['book_formset'] = book_formset
return self.render_to_response(context)
from django.shortcuts import get_object_or_404, redirect
from django.forms import modelformset_factory
from django.urls import reverse_lazy
from django.views.generic import UpdateView,DetailView
from .models import Student, Book
from .forms import StudentForm, BookForm
class EditStudentView(UpdateView):
model = Student
form_class = StudentForm
template_name = 'edit_student.html'
context_object_name = 'student'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get the student object using the ID
student = self.get_object()
# Create the formset for books related to this student
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1)
# Get all books related to the student
context['book_formset'] = BookInlineFormSet(queryset=Book.objects.filter(student=student))
return context
def form_valid(self, form):
# Save the student object first
student = form.save()
# Get the book formset from the POST data
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1)
book_formset = BookInlineFormSet(self.request.POST)
# If the book formset is valid, save the books and link them to the student
if book_formset.is_valid():
books = book_formset.save(commit=False)
for book in books:
book.student = student # Associate the book with the student
book.save()
# Redirect to the student list after successful update
return redirect('student_list')
def form_invalid(self, form):
# Handle invalid forms
context = self.get_context_data(form=form)
# Create Book formset with POST data (to show errors in book forms)
BookInlineFormSet = modelformset_factory(Book, form=BookForm, extra=1)
book_formset = BookInlineFormSet(self.request.POST)
# Add the invalid book formset to the context
context['book_formset'] = book_formset
# Re-render the page with both invalid forms
return self.render_to_response(context)
class StudentDetailView(DetailView):
model = Student
template_name = 'student_detail.html'
context_object_name = 'student'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
student = self.get_object()
books = Book.objects.filter(student=student)
context['books'] = books
return context
Add student.html
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4">Add a New Student</h1>
<form method="POST">
{% csrf_token %}
<!-- Render the Student form with error messages -->
<div class="student-form mb-4">
{{ form.as_p }}
{% for field in form %}
{% if field.errors %}
<div class="alert alert-danger">{{ field.errors }}</div>
{% endif %}
{% endfor %}
</div>
<h2 class="mb-3">Books</h2>
<!-- Management form for formset handling -->
{{ book_formset.management_form }}
<!-- Loop through each Book form in the formset -->
<div class="book-forms">
{% for form in book_formset %}
<div class="book-form mb-4">
<div class="card p-3">
{{ form.as_p }}
{% for field in form %}
{% if field.errors %}
<div class="alert alert-danger">{{ field.errors }}</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Button to add another Book form dynamically -->
<button id="add-form" type="button" class="btn btn-primary mb-3">Add Another Book</button>
<!-- Submit button to create the Student and their Books -->
<button type="submit" class="btn btn-success">Create Student</button><br>
<hr>
<a href="{% url 'student_list' %}" class="btn btn-secondary">Back to Student List</a>
</form>
</div>
<!-- Add Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pzjw8f+ua7Kw1TIq0qPInj0q9UukV5zJcANd5VhTjzldnxpR4pf0kFDMl0u2Ojqn" crossorigin="anonymous"></script>
<script>
// JavaScript for dynamically adding more Book forms
let bookForm = document.querySelectorAll(".book-form");
let container = document.querySelector("form");
let addButton = document.querySelector("#add-form");
let totalForms = document.querySelector("#id_form-TOTAL_FORMS");
let formNum = bookForm.length - 1; // Set initial form number
addButton.addEventListener('click', addForm);
function addForm(e) {
e.preventDefault(); // Prevent default button action
// Clone the first Book form to create a new form
let newForm = bookForm[0].cloneNode(true);
let formRegex = RegExp(`form-(\\d){1}-`, 'g'); // Regex to replace form indices
formNum++; // Increment the form number
newForm.innerHTML = newForm.innerHTML.replace(formRegex, `form-${formNum}-`); // Update indices
container.insertBefore(newForm, addButton); // Insert the new form before the "Add Another Book" button
// Update the total number of forms in the formset
totalForms.setAttribute('value', `${formNum + 1}`);
}
</script>
</body>
</html>