I’m experiencing an issue with my Django project where I’m trying to create a BillInvoice
and its associated BillLineItem
instances. The error I’m encountering is:
"'BillInvoice' instance needs to have a primary key value before this relationship can be used"
Context:
I have a form view (CreateInvoiceView
) that handles the creation of invoices and their line items using a formset. The relevant parts of my code are as follows:
This is my model.py
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)
M views.py
CreateInvoiceView
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)
Error Details:
I am encountering the error when trying to save the BillLineItem
instances. It seems that the BillInvoice
instance does not have a primary key assigned before attempting to create the BillLineItem
instances.
Request:
Could someone please help me identify what might be going wrong? Specifically:
-
Is there an issue with how I’m handling the saving of the
BillInvoice
instance? -
Are there any adjustments needed in my view or model definitions to ensure the
BillInvoice
instance is saved with a primary key before associatingBillLineItem
instances?
What I Have Tried:
Saving the Invoice Immediately: I made sure to save the BillInvoice
instance immediately after creating it, so it gets a primary key assigned before attempting to create any BillLineItem
instances. This should ensure the invoice
has an ID when it’s referenced by the line items.
CreateInvoiceView
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))