You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django创建BillInvoice与关联BillLineItem时主键缺失错误排查

问题:创建BillInvoice及其关联BillLineItem时出现主键错误

上下文

模型代码

UserProfile

class UserProfile(models.Model):
    ROLE = [
        ('Select Role', 'Select Role'),
        ('Admin', 'Admin'),
        ('CBUMT', 'CBUMT'),
        ('Pharmacist', 'Pharmacist'),
        ('Doctor', 'Doctor'),
        ('Nurse', 'Nurse'),
        ('Referral Provider', 'Referral Provider'),
        ('Member', 'Memeber'),
        ('Dependent', 'Dependent'),
    ]

    GENDER = [
        ('None', 'None'),
        ('Male', 'Male'),
        ('Female', 'Female'),
        
    ]
    
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    id_number = models.CharField(max_length=30, blank=True, null=True)
    is_medical_staff = models.BooleanField(default=False)
    role = models.CharField(max_length=50, choices=ROLE, default='CBUMT')
    gender = models.CharField(max_length=20, choices=GENDER, default='None')
    date_of_birth = models.DateField(blank= False, null=False, default=timezone.now)
    phone = models.CharField(max_length=20, blank=True, null=True)
    qualification = models.CharField(max_length=100, blank=True, null=True)
    bio = models.TextField(blank=True, null=True)
    profile_pic = models.ImageField(null=True, blank=True, upload_to='images/', default='img/placeholder.jpg')

    @property
    def age(self):
        today = date.today()
        age = today.year - self.date_of_birth.year - ((today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day))
        return age

    def __str__(self):
        return str(self.user) + ' | ' + self.role

BillInvoice

class BillInvoice(models.Model):
    TYPE_CHOICES = [
        ('Medicine', 'Medicine'),
        ('Auxillary', 'Auxillary'),
        ('Mixed', 'Mixed'),
    ]
    patient = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='invoices', null=True)
    invoice_type = models.CharField(max_length=50, choices=TYPE_CHOICES, default='Medicine')
    reference = models.ForeignKey(Referral, on_delete=models.CASCADE, related_name='invoice_references', null=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
    date = models.DateField(auto_now_add=True)
    due_date = models.DateField(null=True, blank=True)
    status = models.BooleanField(default=False)
    prepared_by = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='prepared_invoices', null=True, blank=True)

    def __str__(self):
        return f'Invoice for {self.patient} on {self.date}'
    
    def update_amount(self):
        total_amount = sum(item.amount for item in self.lineitems.all())
        self.amount = total_amount

    def get_status(self):
        return self.status

    def save(self, *args, **kwargs):
        if not self.id and not self.due_date:
            self.due_date = datetime.now() + timedelta(days=30)
        self.update_amount()  # Update the amount before saving
        super().save(*args, **kwargs)

BillLineItem

class BillLineItem(models.Model):
    patient = models.ForeignKey(BillInvoice, on_delete=models.CASCADE, related_name='lineitems')
    service = models.TextField()
    description = models.TextField()
    quantity = models.IntegerField()
    rate = models.DecimalField(max_digits=9, decimal_places=2)
    amount = models.DecimalField(max_digits=9, decimal_places=2)

    def __str__(self):
        return str(self.patient)

视图代码(初始版本)

class CreateInvoiceView(FormView):
    template_name = 'referral/invoice_create.html'
    form_class = BillInvoiceForm
    success_url = reverse_lazy('ereferral:invoice-list')  # Adjust as needed

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['formset'] = LineItemFormset(self.request.POST)
        else:
            context['formset'] = LineItemFormset()
        context['title'] = "Invoice Generator"
        context['heading_message'] = 'Formset Demo'
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        formset = context['formset']

        if formset.is_valid():
            # Retrieve the UserProfile instance
            patient_profile = form.cleaned_data.get("patient")  # Assuming this returns a UserProfile instance
            
            # Save the BillInvoice instance to get the primary key
            invoice = BillInvoice(
                patient=patient_profile,  # Assuming 'patient' is a ForeignKey to UserProfile
                invoice_type=form.cleaned_data["invoice_type"],
                reference=form.cleaned_data["reference"],
                amount=0  # Initial total_amount, will update later
            )
            invoice.save()  # Now the invoice instance has a primary key

            total = 0
            for item_form in formset:
                if item_form.cleaned_data:  # Ensure the form is not empty
                    service = item_form.cleaned_data.get('service')
                    description = item_form.cleaned_data.get('description')
                    quantity = item_form.cleaned_data.get('quantity')
                    rate = item_form.cleaned_data.get('rate')
                    if service and description and quantity and rate:
                        amount = float(rate) * float(quantity)
                        total += amount
                        BillLineItem.objects.create(
                            invoice=invoice,  # Associating the line item with the invoice
                            service=service,
                            description=description,
                            quantity=quantity,
                            rate=rate,
                            amount=amount
                        )

            # Update the total amount of the invoice after creating all line items
            invoice.total_amount = total
            invoice.save()

            try:
                generate_PDF(self.request, id=invoice.id)
            except Exception as e:
                logger.error(f"PDF generation error: {e}")

            return super().form_valid(form)
        else:
            return self.form_invalid(form)

    def form_invalid(self, form):
        context = self.get_context_data()
        formset = context['formset']
        logger.error("Form or formset is invalid")
        logger.error("Form errors: %s", form.errors)
        logger.error("Formset errors: %s", formset.errors)
        return self.render_to_response(self.get_context_data(form=form))


# Define the formset outside the view class to avoid redefinition
LineItemFormset = formset_factory(LineItemForm, extra=1)

错误详情

保存BillLineItem实例时触发错误:'BillInvoice' instance needs to have a primary key value before this relationship can be used,提示BillInvoice实例在关联明细项前未分配主键。

已尝试操作

调整后的视图代码:

class CreateInvoiceView(FormView):
    template_name = 'referral/invoice_create.html'
    form_class = BillInvoiceForm
    success_url = reverse_lazy('ereferral:invoice-list')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['formset'] = LineItemFormset(self.request.POST)
        else:
            context['formset'] = LineItemFormset()
        context['title'] = "Invoice Generator"
        context['heading_message'] = 'Formset Demo'
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        formset = context['formset']

        if formset.is_valid():
            try:
                # Save the BillInvoice instance
                invoice = form.save(commit=False)
                patient_profile = form.cleaned_data.get("patient")
                invoice.patient = patient_profile
                invoice.total_amount = 0  # Initialize total amount
                invoice.save()

                # Log the invoice primary key to ensure it is saved
                logger.debug(f"Invoice saved with ID: {invoice.id}")

                total = 0
                for item_form in formset:
                    if item_form.cleaned_data:
                        service = item_form.cleaned_data.get('service')
                        description = item_form.cleaned_data.get('description')
                        quantity = item_form.cleaned_data.get('quantity')
                        rate = item_form.cleaned_data.get('rate')
                        if service and description and quantity and rate:
                            amount = float(rate) * float(quantity)
                            total += amount
                            BillLineItem.objects.create(
                                invoice=invoice,
                                service=service,
                                description=description,
                                quantity=quantity,
                                rate=rate,
                                amount=amount
                            )

                # Update the total amount of the invoice after creating all line items
                invoice.total_amount = total
                invoice.save()

                try:
                    generate_PDF(self.request, id=invoice.id)
                except Exception as e:
                    logger.error(f"PDF generation error: {e}")

                return super().form_valid(form)
            except Exception as e:
                logger.error(f"Error saving invoice or line items: {e}")
                return self.form_invalid(form)
        else:
            logger.error("Formset is invalid")
            logger.error(formset.errors)
            return self.form_invalid(form)

    def form_invalid(self, form):
        context = self.get_context_data()
        formset = context['formset']
        logger.error("Form or formset is invalid")
        logger.error("Form errors: %s", form.errors)
        logger.error("Formset errors: %s", formset.errors)
        return self.render_to_response(self.get_context_data(form=form))

需要排查的问题

  • 处理BillInvoice实例保存的方式是否存在问题?
  • 是否需要调整视图或模型定义,确保BillInvoice实例在关联BillLineItem前已保存并拥有主键?

内容的提问来源于stack exchange,提问作者Kennerdol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 17:38:11