BaseFormSet不适用于inlineformset_factory

时间:2019-12-29 10:04:47

标签: django django-forms

我写了我的视图函数来创建新条目。这些条目由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'

1 个答案:

答案 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))+')')