提问人:Adli Abdullah 提问时间:11/5/2023 更新时间:11/6/2023 访问量:33
使用 Django 分页器逐页显示多个 Django 表单,但无法保存页面中的数据
Using Django paginator to display multiple Django forms page by page but unable to save the data from the pages
问:
我正在使用 Django 分页器创建一个输入页面,将多个表单显示为单个页面。有了分页器,我想启用无休止的滚动。
我已经向 ChatGPT 提出了问题,到目前为止,提供的代码没有保存数据。最新的解决方案甚至没有按预期显示“全部保存”按钮。我不确定在更改为下一页之前数据是否实际存储在会话中,因为每次更改页面时,表单都是空白的。
下面是视图函数和 HTML 模板。使用正确的子模板生成页面正在工作。
`# 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"]`
我已经向 chatGPT 寻求解决方案,但到目前为止,每个解决方案都不起作用。
我正在尝试创建一个使用多个表单的输入页面,并使用 django 分页器将每个表单显示为单个页面。一旦我可以将所有表单数据保存到数据库,我将使用分页器表单来启用输入页面的无限滚动。
答:
首先,你尝试实现的功能(同一页面上的多个表单)在 Django 中本质上是艰难的,尽管肯定是可行的。
但是,您目前使用分页器执行此操作的方式是违反直觉的,并且您“反对”更标准的方法的线索是视图的大小。它非常大,并且已经具有用于保存表单的大型外部功能。
另外,你写:
我不确定在更改为下一页之前数据是否实际存储在会话中,因为每次更改页面时,表单都是空白的。
在当前实现中,如果不指定以下内容,则不会将此数据存储到会话中:
request.session.modified = True
您必须在对会话进行任何更改后指定此项。
数据也不会保留在浏览器中,原因是 Django 中的默认分页涉及 GET 查询字符串 URL 参数;因此,它不是单页的。当您从 到 时,您将获得完整的页面刷新,并且没有将任何数据保存到数据库(需要有效的 POST 请求)或浏览器(至少需要一些复杂的 JS 或前端框架);如果没有参数,它也不会保存到会话中。mysite.com/objects/?page=1
mysite.com/objects/?page=2
request.session.modified = True
因此,我的第一个建议是重新考虑这种实现;当然是分页,但可能是整个方法。这似乎过于复杂。
现在,最佳实现将取决于您的业务逻辑,但一些好的起点可能如下所示:
- 首先,有没有办法将所有小的、单独的形式包装成一个大的形式?如果是这样,你会发现行为变得更容易管理。
- 如果没有,是否有具有 M2M 关系的模型可以定义它封装多表单逻辑,然后这是否允许您提供单个表单?同样,如果是这样,您会发现这种实现要容易得多。
如果你不能做到上述任何一项,那很好,尽管会很棘手。你仍然应该肯定地远离默认的 Django 分页。
听起来你希望你的前端有一种“单页”风格的感觉,数据在每个“表单实例”之间随着时间的推移而持续存在。
为此,你要么需要大量自己实现的自定义 vanilla JS,要么需要一些辅助库。就我个人而言,我发现与 Django 很好地集成的两个前端库是 HTMX(对于异步 POST 请求非常有用,您可能会发现您需要实现)和 Alpine(一个非常轻量级的前端库,有点像 Vue,您可以使用它来复制“分页”,但以一种使数据随着时间的推移而持久化的方式)。
然后,对于每个表单,您需要在视图中仔细处理它。如果它是已经存在的对象的表单(即更新表单),请确保正确地向它传递“instance”参数;您可以通过 URL(如果您将其定义为包含实例 ID)或通过表单本身中隐藏的“id”字段来执行此操作。我推荐基于 URL 的方法,因为它是 Django 处理表单的典范,如果你想在同一页面上有多个表单,并且数据会随着时间的推移而持久化,你可以使用 HTMX 来处理对这些表单的更改,每个表单的数据都使用表单实例 ID 的相应 URL 进入端点(即页面上有“多个”表单, 后端逻辑适用于“单个”表单 - 幸福地不知道同一页面上有几个,POST 请求异步发送到不同的端点)。
评论