单个视图中的 Django 多个表单集仅保存最后输入的值
Django multiple formsets in a single view only saves last value entered
我正在尝试在单个视图中使用多个表单集,但我无法正确实现此功能。
例如:当我尝试在每个表单集中添加 3 个值时,即第一个表单集的 3 个条目和第二个表单集的 3 个条目只有最后一个条目存储在数据库中,前两个值被丢弃而不保存。
请找出目前写的代码如下:
1) models.py
from __future__ import unicode_literals
from django.db import models
from bokeh.themes import default
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser
from phonenumber_field.modelfields import PhoneNumberField
# Create your models here.
class FeeForService(models.Model):
CHOICES = (
('Yes', 'Yes'),
('No', 'No'),
)
REQUEST_STATUS = (
('Pending', 'Pending'),
('Approved', 'Approved'),
('Denied', 'Denied'),
)
requestor_name = models.CharField(max_length=240, blank=False, null=False)
requestor_RU_or_PL = models.CharField(max_length=240, blank=False, null=False)
vendor_procurement_system = models.CharField(max_length=3, choices=CHOICES, blank=False, null=False)
vendor_name = models.CharField(max_length=254, blank=False, null=False)
vendor_address = models.CharField(max_length=480, blank=False, null=False)
vendor_email = models.EmailField(max_length=254, blank=False, null=False)
phone_number = PhoneNumberField(blank=True)
proposed_start_date = models.DateTimeField(blank=False, null=False)
proposed_end_date = models.DateTimeField(blank=False, null=False)
brief_proposal = models.CharField(max_length=480, blank=False, null=False)
status = models.CharField(max_length=20, choices=REQUEST_STATUS, default='Pending')
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
class DeliverablesFeeForService(models.Model):
milestone_deliverable = models.CharField('MileStone/Deliverable', max_length=480, blank=False, null=False)
poa_institution = models.CharField('POA/Institution', max_length=480, blank=False, null=False)
duration_to_complete = models.CharField(max_length=240, blank=False, null=False)
feeForService = models.ForeignKey(FeeForService, on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
class PaymentScheduleFeeForService(models.Model):
milestone_deliverable1 = models.CharField('MileStone/Deliverable', max_length=480, blank=False, null=False)
cost = models.DecimalField(max_digits=14, decimal_places=2, blank=False, null=False, default=0)
estimated_payment_date = models.DateTimeField(blank=False, null=False)
feeForService = models.ForeignKey(FeeForService, on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
2) forms.py
from django import forms
from .models import FeeForService, DeliverablesFeeForService, PaymentScheduleFeeForService
from datetime import datetime
from file_resubmit.admin import AdminResubmitImageWidget, AdminResubmitFileWidget
from django.forms.formsets import BaseFormSet
class DeliverablesFeeForServiceForm(forms.ModelForm):
milestone_deliverable = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter milestone/deliverables here',
}),
required=True)
poa_institution = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter POA/Institution here',
}),
required=True)
duration_to_complete = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter duration to complete here',
}),
required=True)
class Meta:
model = DeliverablesFeeForService
exclude = ('feeForService', )
class PaymentScheduleFeeForServiceForm(forms.ModelForm):
milestone_deliverable1 = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter milestone/deliverables here',
}),
required=True)
cost = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter cost here',
}),
required=True)
estimated_payment_date = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter estimated payment date here',
}),
required=True)
class Meta:
model = PaymentScheduleFeeForService
exclude = ('feeForService', )
class DeliverablesFeeForServiceFormset(BaseFormSet):
def clean(self):
"""
Adds validation to check that no two deliverables have the same milestone or institution
and that all deliverables have both an milestone and institution.
"""
if any(self.errors):
return
milestone_deliverables = []
poa_institutions = []
durations_to_complete = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
milestone_deliverable = form.cleaned_data['milestone_deliverable']
poa_institution = form.cleaned_data['poa_institution']
duration_to_complete = form.cleaned_data['duration_to_complete']
# Check that no two deliverables have the same milestone or institution
if milestone_deliverable and poa_institution:
if milestone_deliverable in milestone_deliverables:
duplicates = True
milestone_deliverables.append(milestone_deliverable)
if poa_institution in poa_institutions:
duplicates = True
poa_institutions.append(poa_institution)
if duplicates:
raise forms.ValidationError(
'Deliverables must have unique milestones and institutions.',
code='duplicate_deliverables'
)
# Check that all deliverables have both an milestone and institution
if milestone_deliverable and not poa_institution:
raise forms.ValidationError(
'All deliverables must have an institution.',
code='missing_institution'
)
elif poa_institution and not milestone_deliverable:
raise forms.ValidationError(
'All deliverables must have a milestone.',
code='missing_milestone'
)
class PaymentScheduleFeeForServiceFormset(BaseFormSet):
def clean(self):
"""
Adds validation to check that no two payment schedules have the same milestone
and that all payment schedules have both a milestone and cost.
"""
if any(self.errors):
return
milestone_deliverables1 = []
costs = []
estimated_payment_dates = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
milestone_deliverable1 = form.cleaned_data['milestone_deliverable1']
cost = form.cleaned_data['cost']
estimated_payment_date = form.cleaned_data['estimated_payment_date']
# Check that no two deliverables have the same milestone
if milestone_deliverable1:
if milestone_deliverable1 in milestone_deliverables1:
duplicates = True
milestone_deliverables1.append(milestone_deliverable1)
if duplicates:
raise forms.ValidationError(
'Payment schedule must have unique milestones',
code='duplicate_schedules'
)
# Check that all payment schedules have both a milestone and cost
if milestone_deliverable1 and not cost:
raise forms.ValidationError(
'All payemnt schedules must have a cost.',
code='missing_cost'
)
elif cost and not milestone_deliverable1:
raise forms.ValidationError(
'All payemnt schedules must have a milestone.',
code='missing_milestone'
3) views.py
def create_FeeForService(request):
currentUser = User.objects.get(id=request.user.id)
# Create the formset, specifying the form and formset we want to use.
DeliverablesFormSet = formset_factory(DeliverablesFeeForServiceForm, formset=DeliverablesFeeForServiceFormset)
PaymentScheduleFormSet = formset_factory(PaymentScheduleFeeForServiceForm, formset=PaymentScheduleFeeForServiceFormset)
# This is used as initial data.
deliverable_data = []
paymentSchedule_data = []
if request.method == 'POST': #If the form has been submitted...
feeForService_form = FeeForServiceForm(request.POST) # A form bound to the POST data
deliverables_formset = DeliverablesFormSet(request.POST, prefix='deliverables')
paymentSchedule_formset = PaymentScheduleFormSet(request.POST, prefix='paymentSchedule')
if feeForService_form.is_valid() and deliverables_formset.is_valid() and paymentSchedule_formset.is_valid(): # all validation rules pass
# Save Fee For Service info
feeForService = feeForService_form.save(commit=False)
feeForService.user = request.user
feeForService.save()
# Now save the data for each form in the formset
new_deliverables = []
for deliverable_form in deliverables_formset:
print("Hi i am deliverable for loop")
milestone_deliverable = deliverable_form.cleaned_data.get('milestone_deliverable')
poa_institution = deliverable_form.cleaned_data.get('poa_institution')
duration_to_complete = deliverable_form.cleaned_data.get('duration_to_complete')
if milestone_deliverable and poa_institution and duration_to_complete:
new_deliverables.append(DeliverablesFeeForService(feeForService=feeForService, milestone_deliverable=milestone_deliverable, poa_institution=poa_institution, duration_to_complete=duration_to_complete))
new_paymentSchedules = []
for paymentSchedule_form in paymentSchedule_formset:
milestone_deliverable1 = paymentSchedule_form.cleaned_data.get('milestone_deliverable1')
cost = paymentSchedule_form.cleaned_data.get('cost')
estimated_payment_date = paymentSchedule_form.cleaned_data.get('estimated_payment_date')
if milestone_deliverable1 and cost and estimated_payment_date:
print("2nd Details are:", milestone_deliverable1, cost, estimated_payment_date)
new_paymentSchedules.append(PaymentScheduleFeeForService(feeForService=feeForService, milestone_deliverable1=milestone_deliverable1, cost=cost, estimated_payment_date=estimated_payment_date))
try:
with transaction.atomic():
#Add all the new values
DeliverablesFeeForService.objects.bulk_create(new_deliverables)
PaymentScheduleFeeForService.objects.bulk_create(new_paymentSchedules)
except IntegrityError: #If the transaction failed
messages.error(request, 'There was an error saving your FeeForService.')
return redirect(reverse('create_FeeForService'))
feeForService_form = FeeForServiceForm()
deliverables_formset = DeliverablesFormSet(initial=deliverable_data, prefix='deliverables')
paymentSchedule_formset = PaymentScheduleFormSet(initial=paymentSchedule_data, prefix='paymentSchedule')
else:
feeForService_form = FeeForServiceForm()
deliverables_formset = DeliverablesFormSet(initial=deliverable_data, prefix='deliverables')
paymentSchedule_formset = PaymentScheduleFormSet(initial=paymentSchedule_data, prefix='paymentSchedule')
return render(request, 'createFeeForService.html', {'feeForService_form': feeForService_form, 'deliverables_formset': deliverables_formset, 'paymentSchedule_formset': paymentSchedule_formset})
4) html 代码
{% extends "header.html" %}
{% load widget_tweaks %}
{% block content %}
<script type="text/javascript">
$(function() {
$(".inline.{{ deliverables_formset.prefix }}").formset({
prefix: "{{ deliverables_formset.prefix }}",
})
$(".inline.{{ paymentSchedule_formset.prefix }}").formset({
prefix: "{{ paymentSchedule_formset.prefix }}",
})
})
</script>
<script type="text/javascript">
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function cloneMore(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row.deliverables:not(:last)');
conditionRow.find('.btn.add-form-row')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row').addClass('remove-form-row')
.html('-');
return false;
}
function cloneMore1(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row.payments:not(:last)');
conditionRow.find('.btn.add-form-row1')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row1').addClass('remove-form-row1')
.html('-');
return false;
}
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('.form-row').remove();
var forms = $('.form-row');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row.deliverables:last', 'form');
return false;
});
$(document).on('click', '.remove-form-row', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
$(document).on('click', '.add-form-row1', function(e){
e.preventDefault();
cloneMore1('.form-row.payments:last', 'form');
return false;
});
$(document).on('click', '.remove-form-row1', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
</script>
{% include 'messages.html' %}
<div class="container" align="center">
<h1 class="display-5">Fee For Service</h1>
</div>
<br/>
<form id="form-id" method="post" novalidate>
{% csrf_token %}
{% for hidden_field in feeForService_form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
{% if feeForService_form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in feeForService_form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% for field in feeForService_form.visible_fields %}
<div class="form-group">
<div class="row">
<div class="col-md-8">{{ field.label_tag }}</div>
<div class="col-md-4">
{% if feeForService_form.is_bound %}
{% if field.errors %}
{% render_field field class="form-control is-invalid" %}
{% for error in field.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% else %}
{% render_field field class="form-control is-valid" %}
{% endif %}
{% else %}
{% render_field field class="form-control" %}
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="row"><div class="col-md-12"><p><b>Describe the milestones/deliverables</b></p></div></div>
</div>
<div class="form-group">
<div class="row form-row spacer">
<div class="col-4">
<label>Milestone/Deliverable</label>
</div>
<div class="col-4">
<label>POA/Institution</label>
</div>
<div class="col-4">
<label>Duration to complete</label>
</div>
</div>
</div>
{{ deliverables_formset.management_form }}
{% for deliverables_form in deliverables_formset %}
{{ deliverables_form.id }}
<div class="form-group">
<div class="row form-row spacer deliverables">
<div class="col-4">
<div class="input-group">
{{deliverables_form.milestone_deliverable}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{deliverables_form.poa_institution}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{deliverables_form.duration_to_complete}}
<div class="input-group-append">
<button class="btn btn-success add-form-row">+</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="row"><div class="col-md-12"><p><b>Describe the payment schedule</b></p></div></div>
</div>
<div class="form-group">
<div class="row form-row spacer">
<div class="col-4">
<label>Milestone/Deliverable</label>
</div>
<div class="col-4">
<label>Cost</label>
</div>
<div class="col-4">
<label>Estimated payment date</label>
</div>
</div>
</div>
{{ paymentSchedule_formset.management_form }}
{% for form in paymentSchedule_formset %}
{{ form.id }}
<div class="form-group">
<div class="row form-row spacer payments">
<div class="col-4">
<div class="input-group">
{{form.milestone_deliverable1}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{form.cost}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{form.estimated_payment_date}}
<div class="input-group-append">
<button class="btn btn-success add-form-row1">+</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="row">
<div class="col-xs-12" style="height: 14px;"></div>
</div>
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4" align="center">
<button type="submit" class="btn btn-primary" onclick="return confirm('Do you want to submit this form?')">Submit</button>
</div>
<div class="col-md-4"></div>
</div>
</form>
{% endblock %}
如果您需要有关此问题的更多信息,请告诉我。
任何帮助或建议将不胜感激!
使用@dirkgroten 的提示进行调试后,我发现问题出在我编写的 javascript 代码上。当我尝试添加新的表单集时,我得到的是 NaN 值。
相反,我使用 django-dynamic-formset [https://github.com/elo80ka/django-dynamic-formset] 来实现多个 formset。
请在下面找到实现的脚本代码
<script src="{% static 'js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$(function() {
$(".inline.{{ deliverables_formset.prefix }}").formset({
addText: 'Add Deliverables',
deleteText: 'Remove',
prefix: "{{ deliverables_formset.prefix }}",
})
$(".inline.{{ paymentSchedule_formset.prefix }}").formset({
addText: 'Add Payment Schedules',
deleteText: 'Remove',
prefix: "{{ paymentSchedule_formset.prefix }}",
})
})
</script>
我删除了我的 jQuery 代码并使用了 django-dynamic-formset。
此致,
阿米·凯勒卡
如您所见,问题出在 javascript 代码上。当您尝试添加新的表单集并获得 NaN 值时,因为您的 javascript 代码的表单集名称与原始表单集名称不匹配,因为它以 deliverables 为前缀,第二个带有 付款时间表。
因此,您需要在 javascript 代码中编辑以下函数:
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row.deliverables:last', 'form');
return false;
});
$(document).on('click', '.add-form-row1', function(e){
e.preventDefault();
cloneMore1('.form-row.payments:last', 'form');
return false;
});
将 cloneMore 和 clonemore1 函数中的 'form' 替换为 deliverables 和 paymentSchedule:
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row.deliverables:last', 'deliverables');
return false;
});
$(document).on('click', '.add-form-row1', function(e){
e.preventDefault();
cloneMore1('.form-row.payments:last', 'paymentSchedule');
return false;
});
移除函数也同样如此。
万一将来有人遇到困难,它会有所帮助:)
我正在尝试在单个视图中使用多个表单集,但我无法正确实现此功能。 例如:当我尝试在每个表单集中添加 3 个值时,即第一个表单集的 3 个条目和第二个表单集的 3 个条目只有最后一个条目存储在数据库中,前两个值被丢弃而不保存。
请找出目前写的代码如下:
1) models.py
from __future__ import unicode_literals
from django.db import models
from bokeh.themes import default
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser
from phonenumber_field.modelfields import PhoneNumberField
# Create your models here.
class FeeForService(models.Model):
CHOICES = (
('Yes', 'Yes'),
('No', 'No'),
)
REQUEST_STATUS = (
('Pending', 'Pending'),
('Approved', 'Approved'),
('Denied', 'Denied'),
)
requestor_name = models.CharField(max_length=240, blank=False, null=False)
requestor_RU_or_PL = models.CharField(max_length=240, blank=False, null=False)
vendor_procurement_system = models.CharField(max_length=3, choices=CHOICES, blank=False, null=False)
vendor_name = models.CharField(max_length=254, blank=False, null=False)
vendor_address = models.CharField(max_length=480, blank=False, null=False)
vendor_email = models.EmailField(max_length=254, blank=False, null=False)
phone_number = PhoneNumberField(blank=True)
proposed_start_date = models.DateTimeField(blank=False, null=False)
proposed_end_date = models.DateTimeField(blank=False, null=False)
brief_proposal = models.CharField(max_length=480, blank=False, null=False)
status = models.CharField(max_length=20, choices=REQUEST_STATUS, default='Pending')
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
class DeliverablesFeeForService(models.Model):
milestone_deliverable = models.CharField('MileStone/Deliverable', max_length=480, blank=False, null=False)
poa_institution = models.CharField('POA/Institution', max_length=480, blank=False, null=False)
duration_to_complete = models.CharField(max_length=240, blank=False, null=False)
feeForService = models.ForeignKey(FeeForService, on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
class PaymentScheduleFeeForService(models.Model):
milestone_deliverable1 = models.CharField('MileStone/Deliverable', max_length=480, blank=False, null=False)
cost = models.DecimalField(max_digits=14, decimal_places=2, blank=False, null=False, default=0)
estimated_payment_date = models.DateTimeField(blank=False, null=False)
feeForService = models.ForeignKey(FeeForService, on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
2) forms.py
from django import forms
from .models import FeeForService, DeliverablesFeeForService, PaymentScheduleFeeForService
from datetime import datetime
from file_resubmit.admin import AdminResubmitImageWidget, AdminResubmitFileWidget
from django.forms.formsets import BaseFormSet
class DeliverablesFeeForServiceForm(forms.ModelForm):
milestone_deliverable = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter milestone/deliverables here',
}),
required=True)
poa_institution = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter POA/Institution here',
}),
required=True)
duration_to_complete = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter duration to complete here',
}),
required=True)
class Meta:
model = DeliverablesFeeForService
exclude = ('feeForService', )
class PaymentScheduleFeeForServiceForm(forms.ModelForm):
milestone_deliverable1 = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter milestone/deliverables here',
}),
required=True)
cost = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter cost here',
}),
required=True)
estimated_payment_date = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter estimated payment date here',
}),
required=True)
class Meta:
model = PaymentScheduleFeeForService
exclude = ('feeForService', )
class DeliverablesFeeForServiceFormset(BaseFormSet):
def clean(self):
"""
Adds validation to check that no two deliverables have the same milestone or institution
and that all deliverables have both an milestone and institution.
"""
if any(self.errors):
return
milestone_deliverables = []
poa_institutions = []
durations_to_complete = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
milestone_deliverable = form.cleaned_data['milestone_deliverable']
poa_institution = form.cleaned_data['poa_institution']
duration_to_complete = form.cleaned_data['duration_to_complete']
# Check that no two deliverables have the same milestone or institution
if milestone_deliverable and poa_institution:
if milestone_deliverable in milestone_deliverables:
duplicates = True
milestone_deliverables.append(milestone_deliverable)
if poa_institution in poa_institutions:
duplicates = True
poa_institutions.append(poa_institution)
if duplicates:
raise forms.ValidationError(
'Deliverables must have unique milestones and institutions.',
code='duplicate_deliverables'
)
# Check that all deliverables have both an milestone and institution
if milestone_deliverable and not poa_institution:
raise forms.ValidationError(
'All deliverables must have an institution.',
code='missing_institution'
)
elif poa_institution and not milestone_deliverable:
raise forms.ValidationError(
'All deliverables must have a milestone.',
code='missing_milestone'
)
class PaymentScheduleFeeForServiceFormset(BaseFormSet):
def clean(self):
"""
Adds validation to check that no two payment schedules have the same milestone
and that all payment schedules have both a milestone and cost.
"""
if any(self.errors):
return
milestone_deliverables1 = []
costs = []
estimated_payment_dates = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
milestone_deliverable1 = form.cleaned_data['milestone_deliverable1']
cost = form.cleaned_data['cost']
estimated_payment_date = form.cleaned_data['estimated_payment_date']
# Check that no two deliverables have the same milestone
if milestone_deliverable1:
if milestone_deliverable1 in milestone_deliverables1:
duplicates = True
milestone_deliverables1.append(milestone_deliverable1)
if duplicates:
raise forms.ValidationError(
'Payment schedule must have unique milestones',
code='duplicate_schedules'
)
# Check that all payment schedules have both a milestone and cost
if milestone_deliverable1 and not cost:
raise forms.ValidationError(
'All payemnt schedules must have a cost.',
code='missing_cost'
)
elif cost and not milestone_deliverable1:
raise forms.ValidationError(
'All payemnt schedules must have a milestone.',
code='missing_milestone'
3) views.py
def create_FeeForService(request):
currentUser = User.objects.get(id=request.user.id)
# Create the formset, specifying the form and formset we want to use.
DeliverablesFormSet = formset_factory(DeliverablesFeeForServiceForm, formset=DeliverablesFeeForServiceFormset)
PaymentScheduleFormSet = formset_factory(PaymentScheduleFeeForServiceForm, formset=PaymentScheduleFeeForServiceFormset)
# This is used as initial data.
deliverable_data = []
paymentSchedule_data = []
if request.method == 'POST': #If the form has been submitted...
feeForService_form = FeeForServiceForm(request.POST) # A form bound to the POST data
deliverables_formset = DeliverablesFormSet(request.POST, prefix='deliverables')
paymentSchedule_formset = PaymentScheduleFormSet(request.POST, prefix='paymentSchedule')
if feeForService_form.is_valid() and deliverables_formset.is_valid() and paymentSchedule_formset.is_valid(): # all validation rules pass
# Save Fee For Service info
feeForService = feeForService_form.save(commit=False)
feeForService.user = request.user
feeForService.save()
# Now save the data for each form in the formset
new_deliverables = []
for deliverable_form in deliverables_formset:
print("Hi i am deliverable for loop")
milestone_deliverable = deliverable_form.cleaned_data.get('milestone_deliverable')
poa_institution = deliverable_form.cleaned_data.get('poa_institution')
duration_to_complete = deliverable_form.cleaned_data.get('duration_to_complete')
if milestone_deliverable and poa_institution and duration_to_complete:
new_deliverables.append(DeliverablesFeeForService(feeForService=feeForService, milestone_deliverable=milestone_deliverable, poa_institution=poa_institution, duration_to_complete=duration_to_complete))
new_paymentSchedules = []
for paymentSchedule_form in paymentSchedule_formset:
milestone_deliverable1 = paymentSchedule_form.cleaned_data.get('milestone_deliverable1')
cost = paymentSchedule_form.cleaned_data.get('cost')
estimated_payment_date = paymentSchedule_form.cleaned_data.get('estimated_payment_date')
if milestone_deliverable1 and cost and estimated_payment_date:
print("2nd Details are:", milestone_deliverable1, cost, estimated_payment_date)
new_paymentSchedules.append(PaymentScheduleFeeForService(feeForService=feeForService, milestone_deliverable1=milestone_deliverable1, cost=cost, estimated_payment_date=estimated_payment_date))
try:
with transaction.atomic():
#Add all the new values
DeliverablesFeeForService.objects.bulk_create(new_deliverables)
PaymentScheduleFeeForService.objects.bulk_create(new_paymentSchedules)
except IntegrityError: #If the transaction failed
messages.error(request, 'There was an error saving your FeeForService.')
return redirect(reverse('create_FeeForService'))
feeForService_form = FeeForServiceForm()
deliverables_formset = DeliverablesFormSet(initial=deliverable_data, prefix='deliverables')
paymentSchedule_formset = PaymentScheduleFormSet(initial=paymentSchedule_data, prefix='paymentSchedule')
else:
feeForService_form = FeeForServiceForm()
deliverables_formset = DeliverablesFormSet(initial=deliverable_data, prefix='deliverables')
paymentSchedule_formset = PaymentScheduleFormSet(initial=paymentSchedule_data, prefix='paymentSchedule')
return render(request, 'createFeeForService.html', {'feeForService_form': feeForService_form, 'deliverables_formset': deliverables_formset, 'paymentSchedule_formset': paymentSchedule_formset})
4) html 代码
{% extends "header.html" %}
{% load widget_tweaks %}
{% block content %}
<script type="text/javascript">
$(function() {
$(".inline.{{ deliverables_formset.prefix }}").formset({
prefix: "{{ deliverables_formset.prefix }}",
})
$(".inline.{{ paymentSchedule_formset.prefix }}").formset({
prefix: "{{ paymentSchedule_formset.prefix }}",
})
})
</script>
<script type="text/javascript">
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function cloneMore(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row.deliverables:not(:last)');
conditionRow.find('.btn.add-form-row')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row').addClass('remove-form-row')
.html('-');
return false;
}
function cloneMore1(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row.payments:not(:last)');
conditionRow.find('.btn.add-form-row1')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row1').addClass('remove-form-row1')
.html('-');
return false;
}
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('.form-row').remove();
var forms = $('.form-row');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row.deliverables:last', 'form');
return false;
});
$(document).on('click', '.remove-form-row', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
$(document).on('click', '.add-form-row1', function(e){
e.preventDefault();
cloneMore1('.form-row.payments:last', 'form');
return false;
});
$(document).on('click', '.remove-form-row1', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
</script>
{% include 'messages.html' %}
<div class="container" align="center">
<h1 class="display-5">Fee For Service</h1>
</div>
<br/>
<form id="form-id" method="post" novalidate>
{% csrf_token %}
{% for hidden_field in feeForService_form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
{% if feeForService_form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in feeForService_form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% for field in feeForService_form.visible_fields %}
<div class="form-group">
<div class="row">
<div class="col-md-8">{{ field.label_tag }}</div>
<div class="col-md-4">
{% if feeForService_form.is_bound %}
{% if field.errors %}
{% render_field field class="form-control is-invalid" %}
{% for error in field.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% else %}
{% render_field field class="form-control is-valid" %}
{% endif %}
{% else %}
{% render_field field class="form-control" %}
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="row"><div class="col-md-12"><p><b>Describe the milestones/deliverables</b></p></div></div>
</div>
<div class="form-group">
<div class="row form-row spacer">
<div class="col-4">
<label>Milestone/Deliverable</label>
</div>
<div class="col-4">
<label>POA/Institution</label>
</div>
<div class="col-4">
<label>Duration to complete</label>
</div>
</div>
</div>
{{ deliverables_formset.management_form }}
{% for deliverables_form in deliverables_formset %}
{{ deliverables_form.id }}
<div class="form-group">
<div class="row form-row spacer deliverables">
<div class="col-4">
<div class="input-group">
{{deliverables_form.milestone_deliverable}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{deliverables_form.poa_institution}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{deliverables_form.duration_to_complete}}
<div class="input-group-append">
<button class="btn btn-success add-form-row">+</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="row"><div class="col-md-12"><p><b>Describe the payment schedule</b></p></div></div>
</div>
<div class="form-group">
<div class="row form-row spacer">
<div class="col-4">
<label>Milestone/Deliverable</label>
</div>
<div class="col-4">
<label>Cost</label>
</div>
<div class="col-4">
<label>Estimated payment date</label>
</div>
</div>
</div>
{{ paymentSchedule_formset.management_form }}
{% for form in paymentSchedule_formset %}
{{ form.id }}
<div class="form-group">
<div class="row form-row spacer payments">
<div class="col-4">
<div class="input-group">
{{form.milestone_deliverable1}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{form.cost}}
</div>
</div>
<div class="col-4">
<div class="input-group">
{{form.estimated_payment_date}}
<div class="input-group-append">
<button class="btn btn-success add-form-row1">+</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="row">
<div class="col-xs-12" style="height: 14px;"></div>
</div>
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4" align="center">
<button type="submit" class="btn btn-primary" onclick="return confirm('Do you want to submit this form?')">Submit</button>
</div>
<div class="col-md-4"></div>
</div>
</form>
{% endblock %}
如果您需要有关此问题的更多信息,请告诉我。 任何帮助或建议将不胜感激!
使用@dirkgroten 的提示进行调试后,我发现问题出在我编写的 javascript 代码上。当我尝试添加新的表单集时,我得到的是 NaN 值。
相反,我使用 django-dynamic-formset [https://github.com/elo80ka/django-dynamic-formset] 来实现多个 formset。
请在下面找到实现的脚本代码
<script src="{% static 'js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$(function() {
$(".inline.{{ deliverables_formset.prefix }}").formset({
addText: 'Add Deliverables',
deleteText: 'Remove',
prefix: "{{ deliverables_formset.prefix }}",
})
$(".inline.{{ paymentSchedule_formset.prefix }}").formset({
addText: 'Add Payment Schedules',
deleteText: 'Remove',
prefix: "{{ paymentSchedule_formset.prefix }}",
})
})
</script>
我删除了我的 jQuery 代码并使用了 django-dynamic-formset。
此致, 阿米·凯勒卡
如您所见,问题出在 javascript 代码上。当您尝试添加新的表单集并获得 NaN 值时,因为您的 javascript 代码的表单集名称与原始表单集名称不匹配,因为它以 deliverables 为前缀,第二个带有 付款时间表。 因此,您需要在 javascript 代码中编辑以下函数:
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row.deliverables:last', 'form');
return false;
});
$(document).on('click', '.add-form-row1', function(e){
e.preventDefault();
cloneMore1('.form-row.payments:last', 'form');
return false;
});
将 cloneMore 和 clonemore1 函数中的 'form' 替换为 deliverables 和 paymentSchedule:
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row.deliverables:last', 'deliverables');
return false;
});
$(document).on('click', '.add-form-row1', function(e){
e.preventDefault();
cloneMore1('.form-row.payments:last', 'paymentSchedule');
return false;
});
移除函数也同样如此。 万一将来有人遇到困难,它会有所帮助:)