> For the complete documentation index, see [llms.txt](https://olee-tech.gitbook.io/django/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://olee-tech.gitbook.io/django/formset/page-1.md).

# Page 1

##

{% file src="/files/2F4cJV6B55PQ8FNMCIto" %}

## Create Project

```bash
Django-admin startproject myproject
```

## Create App

```bash
python manage.py startapp library
```

## **Link App With Project**

**Add App With Django Project**

```python

INSTALLED_APPS = [
    'library',
]
```

**project urls.py**

```
path('students/', include('library.urls')),
```

### **Urls.py**

**library/urls.py**

```
from django.urls import path
from .views import author_create, author_list,author_update,author_delete

urlpatterns = [
    path('author/create/', author_create, name='author_create'),
    path('author/list/', author_list, name='author_list'),
    path('author/update/<int:author_id>/', author_update, name='author_update'),
    path('author/delete/<int:author_id>/', author_delete, name='author_delete'),


]
```

## Models

**models.py**

```python
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    category = models.CharField(max_length=100,default='a')

    def __str__(self):
        return self.title

```

## **Forms**

**forms.py**

```python
from django import forms
from django.forms.models import inlineformset_factory
from .models import Author, Book

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ['name']


BookFormSet = inlineformset_factory(
    Author,
    Book,
    fields=('title','category',),
    extra=1,
    can_delete=False,
    min_num=1,
    validate_min=True
)

```

## Views

**views.py**

```python
from django.shortcuts import render, redirect,get_object_or_404
from .forms import AuthorForm, BookFormSet
from .models import Author,Book

def author_create(request):
    if request.method == 'POST':
        form = AuthorForm(request.POST)
        formset = BookFormSet(request.POST)

        if form.is_valid() and formset.is_valid():
            author = form.save()
            formset.instance = author
            formset.save()
            return redirect('author_list')
    else:
        form = AuthorForm()
        formset = BookFormSet()

    context = {
        'form': form,
        'formset': formset,
    }
    return render(request, 'library/author_create.html', context)


def author_list(request):
    authors = Author.objects.all()
    context = {'authors': authors}
    return render(request, 'library/author_list.html', context)

def author_update(request, author_id):
    author = get_object_or_404(Author, id=author_id)
    if request.method == 'POST':
        form = AuthorForm(request.POST, instance=author)
        formset = BookFormSet(request.POST, instance=author)

        if form.is_valid() and formset.is_valid():
            form.save()
            formset.save()
            return redirect('author_list')
    else:
        form = AuthorForm(instance=author)
        formset = BookFormSet(instance=author)

    context = {
        'form': form,
        'formset': formset,
    }
    return render(request, 'library/author_update.html', context)

def author_delete(request, author_id):
    author = get_object_or_404(Author, id=author_id)
    if request.method == 'POST':
        author.delete()
        return redirect('author_list')

    context = {
        'author': author
    }
    return render(request, 'library/author_delete.html', context)    
```

## **Templates**

**templates/library/author\_create.html**

```html
<!DOCTYPE html>
<html>
<head>
    <title>Create Author</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            $('.add-form').click(function() {
                cloneForm($(this).data('form-prefix'));
            });

            $('.remove-form').click(function() {
                removeForm($(this).data('form-prefix'));
            });

            function cloneForm(formPrefix) {
                const formContainer = $('.form-container').last();
                const totalForms = parseInt($('#id_' + formPrefix + '-TOTAL_FORMS').val());
                const newForm = formContainer.clone();
                
                newForm.find('input').val('');
                newForm.find('select').val('');
                newForm.find('input[type="hidden"]').remove();
                newForm.find('label.error').remove();

                formContainer.after(newForm);

                newForm.find('input, select').each(function() {
                    updateFormElementIndex($(this), formPrefix, totalForms);
                });

                $('#id_' + formPrefix + '-TOTAL_FORMS').val(totalForms + 1);
            }

            function removeForm(formPrefix) {
                const formContainer = $('.form-container');
                if (formContainer.length > 1) {
                    formContainer.last().remove();
                    updateFormIndex(formPrefix);
                }
            }

            function updateFormElementIndex(elem, formPrefix, formIndex) {
                const idRegex = new RegExp(formPrefix + '-(\\d+|__prefix__)-');
                const replacement = formPrefix + '-' + formIndex + '-';
                elem.attr('id', elem.attr('id').replace(idRegex, replacement));
                elem.attr('name', elem.attr('name').replace(idRegex, replacement));
                elem.attr('for', elem.attr('for').replace(idRegex, replacement));
            }

            function updateFormIndex(formPrefix) {
                const formContainer = $('.form-container');
                formContainer.each(function(index) {
                    $(this).find('input, select').each(function() {
                        updateFormElementIndex($(this), formPrefix, index);
                    });
                });
                $('#id_' + formPrefix + '-TOTAL_FORMS').val(formContainer.length);
            }
        });
    </script>
</head>
<body>
    <h1>Create Author</h1>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        {{ formset.management_form }}
        <div class="formset-container">
            {% for form in formset %}
                <div class="form-container">
                    {{ form.as_table }}
                    {% if forloop.first %}
                        <button type="button" class="add-form" data-form-prefix="{{ formset.prefix }}">Add Book</button>
                    {% else %}
                        <button type="button" class="remove-form" data-form-prefix="{{ formset.prefix }}">Remove</button>
                    {% endif %}
                </div>
            {% endfor %}
        </div>
        <button type="submit">Save</button>
    </form>
</body>
</html>

```

**templates/library/author\_update.html**

```html
<!DOCTYPE html>
<html>
<head>
    <title>Update Author</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function() {
            $('.add-form').click(function() {
                cloneForm($(this).data('form-prefix'));
            });

            $('.delete-form').click(function() {
                deleteForm($(this).closest('.form-container'));
            });

            function cloneForm(formPrefix) {
                const formContainer = $('.form-container').last();
                const totalForms = parseInt($('#id_' + formPrefix + '-TOTAL_FORMS').val());
                const newForm = formContainer.clone();
                
                newForm.find('input').val('');
                newForm.find('select').val('');
                newForm.find('input[type="checkbox"]').prop('checked', false);
                newForm.find('input[type="hidden"]').remove();
                newForm.find('label.error').remove();

                formContainer.after(newForm);

                newForm.find('input, select').each(function() {
                    updateFormElementIndex($(this), formPrefix, totalForms);
                });

                $('#id_' + formPrefix + '-TOTAL_FORMS').val(totalForms + 1);
            }

            function deleteForm(formContainer) {
                const totalForms = parseInt($('#id_' + formContainer.data('form-prefix') + '-TOTAL_FORMS').val());

                if (totalForms > 1) {
                    formContainer.remove();
                    updateFormIndices(formContainer.data('form-prefix'));
                    $('#id_' + formContainer.data('form-prefix') + '-TOTAL_FORMS').val(totalForms - 1);
                }
            }

            function updateFormElementIndex(elem, formPrefix, formIndex) {
                const idRegex = new RegExp(formPrefix + '-(\\d+|__prefix__)-');
                const replacement = formPrefix + '-' + formIndex + '-';
                elem.attr('id', elem.attr('id').replace(idRegex, replacement));
                elem.attr('name', elem.attr('name').replace(idRegex, replacement));
                elem.attr('for', elem.attr('for').replace(idRegex, replacement));
            }

            function updateFormIndices(formPrefix) {
                $('.form-container').each(function(index) {
                    updateFormElementIndex($(this), formPrefix, index);
                });
            }
        });
    </script>
</head>
<body>
    <h1>Update Author</h1>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        {{ formset.management_form }}
        <div class="formset-container">
            {% for form in formset %}
                <div class="form-container">
                    {{ form.as_table }}
                    {% if forloop.last %}
                        <button type="button" class="add-form" data-form-prefix="{{ formset.prefix }}">Add Book</button>
                    {% else %}
                        <button type="button" class="delete-form">Remove Book</button>
                    {% endif %}
                </div>
            {% endfor %}
        </div>
        <button type="submit">Save</button>
    </form>
</body>
</html>

```

**templates/library/author\_list.html**

```html
<!DOCTYPE html>
<html>
<head>
    <title>Author List</title>
</head>
<body>
    <h1>Author List</h1>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            {% for author in authors %}
                <tr>
                    <td>{{ author.name }}</td>
                    <td>
                        <a href="{% url 'author_update' author.id %}">Edit</a>
                        <a href="{% url 'author_delete' author.id %}">Delete</a>
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
    <a href="{% url 'author_create' %}">Add Author</a>
</body>
</html>

```

**templates/library/author\_delete.html**

```markup
<!DOCTYPE html>
<html>
<head>
    <title>Delete Author</title>
</head>
<body>
    <h1>Delete Author</h1>
    <p>Are you sure you want to delete the author "{{ author.name }}"?</p>
    <form method="post">
        {% csrf_token %}
        <button type="submit">Delete</button>
        <a href="{% url 'author_list' %}">Cancel</a>
    </form>
</body>
</html>

```

##


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://olee-tech.gitbook.io/django/formset/page-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
