About add sub-subform for dynamically added subforms

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>

Side Note: When posting code here, enclose the code between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This forces the forum software to keep your code properly formatted. (I’ve taken the liberty of adjusting your original code for this, please remember to do this in the future.)

To directly answer your question, yes. With some JavaScript (or HTMX) and specialized views, you can create the html for a formset in Django, and then inject that into the current page.

This is a topic that has been discussed a few times previously.

A couple of the more detailed threads are at:

(There may be others, these are just the first best two I could find.)