I am using Django paginator to create an input page that display multiple forms as single pages. With the paginator in place i would like to enable endless scrolling.
I have asked questions to ChatGPT and so far the code provided does not save the data. The latest solution does not even show the save all button as intended. I am not sure if the data is actually stored in the session before changing to the next page as every time i change page, the form is blank.
below is the view function and html template. The generation of the page with the correct sub template is working.
`# views.py
from django.core.paginator import Paginator
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse
from django.contrib.auth import authenticate, logout
from django.contrib import messages
from django.http import JsonResponse, HttpResponseRedirect
from django.forms.models import model_to_dict
from django.db import transaction
...
# Define a function to handle the final save operation
def save_all_forms(form_classes, form_data_list):
# Start a database transaction
with transaction.atomic():
# Save the first form instance
first_form_class = form_classes[0]
first_form_instance = first_form_class(**form_data_list[0]).save()
# Save subsequent form instances
instances = [first_form_instance]
for form_class, form_data in zip(form_classes[1:], form_data_list[1:]):
form_instance = form_class(**form_data)
# Assuming the foreign key to the first form is named 'page1'
setattr(form_instance, 'page1', first_form_instance)
form_instance.save()
instances.append(form_instance)
# Convert model instances to dictionaries for any further processing
instances_dict = [model_to_dict(instance) for instance in instances]
return instances_dict # This list can be used for further processing if needed
def ler_new_pages_application_fraud(request):
form_classes = [LossEventPage1Form, LossEventPage2Form, DummyForm]
form_data_list = request.session.get('form_data_list', [{} for _ in form_classes])
all_forms_valid = all(form_data for form_data in form_data_list)
if request.method == 'POST':
# This is the page of the current form to validate.
page_number = request.GET.get('page', 1)
try:
page_number = int(page_number)
except ValueError:
page_number = 1
page_number = max(1, min(page_number, len(form_classes)))
# Get the form class for the current form to validate
current_form_class = form_classes[page_number - 1]
current_form = current_form_class(request.POST, request.FILES)
if current_form.is_valid():
# Store the cleaned data from the form into the session
form_data_list[page_number - 1] = current_form.cleaned_data
request.session['form_data_list'] = form_data_list
all_forms_valid = all(form_data for form_data in form_data_list)
if 'save_all' in request.POST and all_forms_valid:
# Save all forms here
instances = save_all_forms(form_classes, form_data_list)
del request.session['form_data_list'] # Clear the session data after saving
return redirect('ler_listing') # Redirect to a success page
elif page_number < len(form_classes):
# Redirect to the next form page
return redirect(f"{reverse('pages-application-fraud')}?page={page_number + 1}")
# If not POST or forms are not valid, display current form
page_number = request.GET.get('page', 1)
try:
page_number = int(page_number)
except ValueError:
page_number = 1
page_number = max(1, min(page_number, len(form_classes)))
current_form_class = form_classes[page_number - 1]
current_form = current_form_class(initial=form_data_list[page_number - 1])
paginator = Paginator(form_classes, 1)
page_obj = paginator.get_page(page_number)
context = {
'form': current_form,
'page_obj': page_obj,
'all_forms_valid': all_forms_valid,
}
return render(request, 'ler_new_pages_application_fraud.html', context)
#html template
{% extends 'base.html' %}
{% load static %}
{% block content %}
{% if all_forms_valid %}
<form method="post">
{% csrf_token %}
<button name="save_all" type="submit">Save All</button>
</form>
{% else %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if page_obj.number == 1 %}
{% include 'subtemplate/lerCommonPage01p.html' %}
{% endif %}
{% if page_obj.number == 2 %}
{% include 'subtemplate/lerCommonPage02p.html' %}
{% endif %}
{% if page_obj.number == 3 %}
{% include 'subtemplate/lerCommonPage03.html' %}
{% endif %}
</form>
{% endif %}
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
<script src="{% static 'js/jquery_3_5_1.min.js'%}"></script>
<!-- Add the following JavaScript code -->
<script>
$(document).ready(function () {
var max_length = 1500; // Set your desired maximum character length
var descriptionField = $('#id_incidentSummary'); // Replace 'id_description' with your field's ID
descriptionField.on('input', function () {
var current_length = descriptionField.val().length;
var remaining = max_length - current_length;
$('#char-count-incidentSummary').text(remaining + ' characters remaining');
});
});
</script>
{% endblock %}
#models.py
class LossEventPage1(models.Model):
code = models.CharField(default=1, max_length=30)
description = models.CharField(max_length=512)
reportingEntity = models.ManyToManyField(website.models.ReportingEntity)
id_Org = models.ForeignKey(website.models.Org, on_delete=models.SET_NULL, null=True)
incidentSummary = models.CharField(max_length=1000)
id_Location = models.ForeignKey(
website.models.Location, on_delete=models.SET_NULL, null=True
)
locationDesc = models.CharField(max_length=4000)
timesurvey = models.TimeField() # Time Of Event Detection
date_survey = models.DateField() # Date Of Event Detection
amount_involved = models.DecimalField(max_digits=12, decimal_places=2)
amount_involved_estd = models.DecimalField(max_digits=12, decimal_places=2)
class LossEventPage2(models.Model):
lossEventPage1 = models.OneToOneField(
LossEventPage1, on_delete=models.CASCADE, null=True,
related_name='losseventpage2'
)
incidentSummary = models.CharField(max_length=1000)
#forms.py
from django import forms
class DummyForm(forms.Form):
pass # No form fields required
class LossEventPage1Form(forms.ModelForm):
date_survey = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
timesurvey = forms.TimeField(widget=forms.TimeInput(attrs={"type": "time"}))
class Meta:
model = LossEventPage1
fields = [
"description",
"reportingEntity",
"id_Org",
"timesurvey",
"date_survey",
"id_Location",
"locationDesc",
"amount_involved",
"amount_involved_estd",
]
widgets = {
"reportingEntity": forms.CheckboxSelectMultiple,
}
class LossEventPage2Form(forms.ModelForm):
incidentSummary = forms.CharField(
widget=forms.Textarea(attrs={"rows": 4, "cols": 50, "maxlength": 1000}),
label="Incident Summary",
help_text="Enter a description (max 1000 characters)",
)
class Meta:
model = LossEventPage2
fields = ["incidentSummary"]`
i have asked chatGPT for solutions and each solution does not work so far.
I am trying to create an input page that uses multiple forms and to show each form as a single page with django paginator. Once I can save all form data to Database I am going to use the paginator form to enable endless scroll of the input page.
To begin with, the functionality you are trying to implement (multiple forms on the same page) is inherently tough in Django, albeit certainly doable.
However, the way you're currently doing it with paginators is counter-intuitive, and the clue that you're 'working against' the more standard way to do this is the size of your view. It is very large, and that's already with a large external function for form saving.
Also, you write:
I am not sure if the data is actually stored in the session before changing to the next page as every time i change page, the form is blank.
In your current implementation, this data will not be stored to the session without specifying the following:
request.session.modified = True
And you must specify this after any change to the session.
Data also won't persist in the browser, and the reason is that default pagination in Django involves GET Querystring URL Parameters; therefore it is not single-page. When you go from mysite.com/objects/?page=1
to mysite.com/objects/?page=2
, you get a complete page refresh with no data saved either to the database (requires a valid POST request) or to your browser (requires at a minimum some complex JS or a frontend framework); and without the request.session.modified = True
argument, it's not saving to the session either.
Therefore, my first recommendation would be to rethink this implementation; certainly the pagination, but potentially the whole approach. It seems overly complex.
Now the best implementation will depend on your business logic, but some good places to start may be as follows:
If you cannot do either of the above, then that is fine although it will be tricky. You should still move away from default Django Pagination for sure.
It sounds like you want a 'single-page' style feel for your frontend, with data persisting over time between each 'form instance'.
For this you will either need a lot of custom vanilla JS you implement yourself, or some helper libraries. Personally, I find the best two frontend libraries for this that integrate nicely with Django are HTMX (hugely useful for async POST requests, which you may find you need to implement) and Alpine (a very lightweight frontend lib, a bit like Vue, which you can probably use to replicate the 'pagination' but in a way that makes data persist over time).
Then for each Form, you will need to handle it carefully inside your views. If it is a Form for an object that already exists (ie. an update form), then make sure you are correctly passing it the 'instance' parameter; you can do this either through the URL (if you define it to include the instance id) or through a hidden 'id' field in the form itself. I would recommend the URL based approach, as it is paradigmatically how Django is built to deal with forms, and if you then want to have multiple forms on the same page with data persisting over time you can use HTMX to handle changes to those forms, with data from each form going to the endpoint with the appropriate URL for the form instance's ID (i.e. you have 'multiple' forms on the page, with the backend logic working nicely for 'single' forms - blissfully unaware that there are several on the same page, with the POST requests going asynchronously to different endpoints).