我写了我的视图函数来创建新条目。这些条目由JournalEntry()组成,然后与之相关的许多LineItem()都存在。
我创建了一个BaseFormSet,它可以对LineItems进行验证(检查它们是否正确汇总)。
很好,但是当我尝试将相同的BaseFormSet应用于Update视图中的inlineformset_factory()时,它不起作用( init ()得到了意外的关键字参数'instance')。
那么BaseFormSet不能与嵌入式表单集一起使用吗?如果没有,那么更新记录时如何在表单集之间进行验证?
谢谢。
# Function to create new entry
def entries_new(request):
LineItemFormSet = formset_factory(LineItemForm, formset=BaseLineItemFormSet, extra=0, min_num=2, validate_min=True)
if request.method == 'POST':
journal_entry_form = JournalEntryForm(request.POST)
lineitem_formset = LineItemFormSet(request.POST)
if journal_entry_form.is_valid() and lineitem_formset.is_valid():
journal_entry = journal_entry_form.save(commit=False)
journal_entry.user = request.user
journal_entry.type = 'JE'
journal_entry.save()
lineitems = lineitem_formset
for lineitem in lineitems:
obj=lineitem.save(commit=False)
obj.journal_entry = journal_entry
obj.save()
messages.success(request, "Journal entry successfully created.")
return HttpResponseRedirect(reverse('journal:entries_show_detail', kwargs={'pk': journal_entry.id}) )
else:
journal_entry_form = JournalEntryForm(initial = {'date': datetime.date.today().strftime('%Y-%m-%d')})
lineitem_formset = LineItemFormSet()
return render(request, 'journal/entries_new.html', {'journal_entry_form': journal_entry_form, 'lineitem_formset': lineitem_formset})
@login_required
def entries_update(request, pk):
journal_entry = get_object_or_404(JournalEntry, pk=pk)
#Convert date format to be suitable for Datepicker input.
journal_entry.date = journal_entry.date.strftime('%Y-%m-%d')
journal_entry_form = JournalEntryForm(instance=journal_entry)
# Doesn't work with BaseForm so now we have no cross formset validation. NEEDS FIXING.
# LineItemFormSet = inlineformset_factory(JournalEntry, LineItem, formset=BaseLineItemFormSet, fields=('ledger','description','project','cr','dr'), extra=0)
LineItemFormSet = inlineformset_factory(JournalEntry, LineItem, fields=('ledger','description','project','cr','dr'), extra=0)
lineitem_formset = LineItemFormSet(instance=journal_entry)
if request.method == 'POST':
lineitem_formset = LineItemFormSet(request.POST, instance=journal_entry)
journal_entry_form = JournalEntryForm(request.POST, instance=journal_entry)
if lineitem_formset.is_valid() and journal_entry_form.is_valid:
journal_entry_form.save()
lineitem_formset.save()
messages.success(request, "Journal entry successfully updated.")
return HttpResponseRedirect(reverse('journal:entries_show_detail', kwargs={'pk': journal_entry.id}) )
return render(request, 'journal/entries_update.html',{'journal_entry': journal_entry, 'journal_entry_form': journal_entry_form, 'lineitem_formset': lineitem_formset })
forms.py
# It is used here in order to validate *across* formsets.
class BaseLineItemFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
# If any forms have errors then don't continue.
return
cr = 0
dr = 0
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
#Skips any records that are scheduled for deletion
continue
if form.cleaned_data.get('cr'):
cr += form.cleaned_data.get('cr')
if form.cleaned_data.get('dr'):
dr += form.cleaned_data.get('dr')
logger.warning('CR:' + str(cr) + ' DR:' + str(dr))
if cr != dr:
raise forms.ValidationError('The CRs and DRs do not balance. (Sum CR:'+str(cr)+', Sum DR:'+str(dr)+', Difference:'+str(abs(cr-dr))+')')
models.py
class JournalEntry(models.Model):
# User needs to be set back to compulsory !!!
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True)
date = models.DateField(null=False, blank=False)
TYPE = (
('BP', 'Bank Payment'),
('YE', 'Year End'),
('JE', 'Journal Entry')
)
type = models.CharField(
max_length=2,
choices=TYPE,
blank=True,
default='0'
)
description = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
if self.description:
return self.description
else:
return 'Journal Entry' + str(self.id)
class Meta(object):
ordering = ['id']
verbose_name_plural = 'Journal entries'
class LineItem(models.Model):
journal_entry = models.ForeignKey(JournalEntry, on_delete=models.CASCADE)
ledger = models.ForeignKey(Ledger, on_delete=models.PROTECT)
description = models.CharField(max_length=255, null=True, blank=True)
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True)
cr = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
dr = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
STATUS = (
('0', 'Not reconciled'),
('1', 'Draft'),
)
status = models.CharField(
max_length=1,
choices=STATUS,
default='0'
)
reconciliation_date = models.DateField(null=True, blank=True)
#def __str__(self):
# return self.description
class Meta(object):
ordering = ['id']
追踪
Environment:
Request Method: GET
Request URL: http://localhost/journal/entries/update/153/
Django Version: 3.0
Python Version: 3.8.0
Installed Applications:
['Journal',
'adminsortable2',
'widget_tweaks',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback (most recent call last):
File "C:\Users\Philip\CodeRepos\Acacia2\venv\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Users\Philip\CodeRepos\Acacia2\venv\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\Philip\CodeRepos\Acacia2\venv\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Philip\AppData\Local\Programs\Python\Python38-32\lib\contextlib.py", line 75, in inner
return func(*args, **kwds)
File "C:\Users\Philip\CodeRepos\Acacia2\venv\lib\site-packages\django\contrib\auth\decorators.py", line 21, in _wrapped_view
return view_func(request, *args, **kwargs)
File "C:\Users\Philip\CodeRepos\Acacia2\Journal\views.py", line 55, in entries_update
lineitem_formset = LineItemFormSet(instance=journal_entry)
Exception Type: TypeError at /journal/entries/update/153/
Exception Value: __init__() got an unexpected keyword argument 'instance'
答案 0 :(得分:0)
按照Iain的建议,必须使用BaseInlineFormSet创建单独的Base类:
# It is used here in order to validate *across* formsets.
class BaseInlineLineItemFormSet(BaseInlineFormSet):
def clean(self):
super(BaseInlineLineItemFormSet, self).clean()
# example custom validation across forms in the formset
cr = 0
dr = 0
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
#Skips any records that are scheduled for deletion
continue
if form.cleaned_data.get('cr'):
cr += form.cleaned_data.get('cr')
if form.cleaned_data.get('dr'):
dr += form.cleaned_data.get('dr')
logger.warning('Lineitem: CR=' + str(cr) + ' DR=' + str(dr))
logger.warning('Lineitem totals: CR=' + str(cr) + ' DR=' + str(dr))
if cr != dr:
raise forms.ValidationError('The CRs and DRs do not balance. (Sum CR:'+str(cr)+', Sum DR:'+str(dr)+', Difference:'+str(abs(cr-dr))+')')