I have a form where a user can select the title of an object and then save it but when I do hit save, I get an error. I am very certain that this problem is because I am using ModelChoiceField which returns ids of objects instead of the instances of that object.
So, I have two models: InvoiceModel
class Invoice(models.Model):
line_one = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 1", blank=True, null=True, default='')
line_one_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_one_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_one_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
Invoice.line_one is referenced to Inventory.product_number.
InventoryModel:
class Inventory(models.Model):
product_number = models.IntegerField(primary_key=True)
product = models.TextField(max_length=3000, default='', blank=True, null=True)
title = models.CharField('Title', max_length=120, default='', blank=True, null=True, unique=True)
amount = models.IntegerField('Unit Price', default=0, blank=True, null=True)
def __str__(self):
return self.title
InvoiceForm:
class InvoiceForm(forms.ModelForm):
line_one = forms.ModelChoiceField(queryset=Inventory.objects.values_list('title', flat=True), label="Line 1")
line_one_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
class Meta:
model = Invoice
fields = ['line_one',#...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['line_one_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
I actually have 10 lines, [i.e., line_two, etc..] but I am just pasting the code for line_one here for simplicity.
I also want only the amount of the object selected in line_one to be displayed in line_one_unit_price. Please let me know if you know how to achieve that.
views.py:
@login_required
def add_invoice(request):
form = InvoiceForm(request.POST or None)
total_invoices = Invoice.objects.count()
queryset = Invoice.objects.order_by('-invoice_date')[:6]
if form.is_valid():
form.save()
messages.success(request, 'Successfully Saved')
return redirect('/invoice/list_invoice')
context = {
"form": form,
"title": "New Invoice",
"total_invoices": total_invoices,
"queryset": queryset,
}
return render(request, "entry.html", context)
The error that I get:
Thank you!
Edit:
I edited the forms.py and removed the line of code that was overriding line_one But now I get an Integrity Error.
**InvoiceForm:**
class InvoiceForm(forms.ModelForm):
line_one_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
class Meta:
model = Invoice
fields = ['line_one',#...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['line_one_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
Error:
IntegrityError at /invoice/add_invoice/
NOT NULL constraint failed: invoicecemgmt_invoice.line_three_id
Request Method: POST
Request URL: http://127.0.0.1:8000/invoice/add_invoice/
Django Version: 4.0
Exception Type: IntegrityError
Exception Value:
NOT NULL constraint failed: invoicecemgmt_invoice.line_three_id
Edit:
The full models.py code:
class Invoice(models.Model):
comments = models.TextField(max_length=3000, default='', blank=True, null=True)
invoice_number = models.IntegerField(blank=True, null=True)
invoice_date = models.DateField(auto_now_add=False, auto_now=False, blank=True, null=True)
name = models.CharField('Customer Name', max_length=120, default='', blank=True, null=True)
line_one = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 1", blank=True, default='')
line_one_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_one_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_one_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_two = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 2", blank=True, default='')
line_two_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_two_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_two_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_three = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 3", blank=True, default='')
line_three_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_three_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_three_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_four = models.ForeignKey(Inventory, on_delete=models.CASCADE,related_name='+', verbose_name="Line 4", blank=True, default='')
line_four_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_four_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_four_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_five = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 5", blank=True, default='')
line_five_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_five_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_five_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_six = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 6", blank=True, default='')
line_six_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_six_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_six_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_seven = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 7", blank=True, default='')
line_seven_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_seven_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_seven_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_eight = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 8", blank=True, default='')
line_eight_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_eight_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_eight_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_nine = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 9", blank=True, default='')
line_nine_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_nine_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_nine_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_ten = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 10", blank=True, default='')
line_ten_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_ten_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_ten_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
phone_number = models.CharField(max_length=120, default='', blank=True, null=True)
total = models.IntegerField(default='0', blank=True, null=True)
balance = models.IntegerField(default='0', blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
last_updated = models.DateTimeField(auto_now_add=False, auto_now=True, blank=True)
paid = models.BooleanField(default=False)
invoice_type_choice = (
('Invoice', 'Invoice'),
('Receipt', 'Receipt'),
)
invoice_type = models.CharField(max_length=50, blank=True, null=True, choices=invoice_type_choice)
def __unicode__(self):
return self.invoice_number
forms.py:
class InvoiceForm(forms.ModelForm):
line_one_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_two_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_three_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_four_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_five_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_six_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_seven_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_eight_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_nine_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
line_ten_unit_price = forms.CharField(widget=forms.Select, label="Unit Price(₹)")
class Meta:
model = Invoice
fields = ['name', 'phone_number', 'invoice_date', 'invoice_number',
'line_one', 'line_one_quantity', 'line_one_unit_price', 'line_one_total_price',
'line_two', 'line_two_quantity', 'line_two_unit_price', 'line_two_total_price',
'line_three', 'line_three_quantity', 'line_three_unit_price', 'line_three_total_price',
'line_four', 'line_four_quantity', 'line_four_unit_price', 'line_four_total_price',
'line_five', 'line_five_quantity', 'line_five_unit_price', 'line_five_total_price',
'line_six', 'line_six_quantity', 'line_six_unit_price', 'line_six_total_price',
'line_seven', 'line_seven_quantity', 'line_seven_unit_price', 'line_seven_total_price',
'line_eight', 'line_eight_quantity', 'line_eight_unit_price', 'line_eight_total_price',
'line_nine', 'line_nine_quantity', 'line_nine_unit_price', 'line_nine_total_price',
'line_ten', 'line_ten_quantity', 'line_ten_unit_price', 'line_ten_total_price',
'total', 'paid', 'invoice_type'
]
widgets = {
'line_one_quantity': TextInput(),
'line_two_quantity': TextInput(),
'line_three_quantity': TextInput(),
'line_four_quantity': TextInput(),
'line_five_quantity': TextInput(),
'line_six_quantity': TextInput(),
'line_seven_quantity': TextInput(),
'line_eight_quantity': TextInput(),
'line_nine_quantity': TextInput(),
'line_ten_quantity': TextInput(),
}
def clean_invoice_number(self):
invoice_number = self.cleaned_data.get('invoice_number')
if not invoice_number:
raise forms.ValidationError('This field is required')
return invoice_number
def clean_name(self):
name = self.cleaned_data.get('name')
if not name:
raise forms.ValidationError('This field is required')
return name
def clean_line_one(self):
line_one = self.cleaned_data.get('line_one')
if not line_one:
raise forms.ValidationError('This field is required')
return line_one
def clean_line_one_quantity(self):
line_one_quantity = self.cleaned_data.get('line_one_quantity')
if not line_one_quantity:
raise forms.ValidationError('This field is required')
return line_one_quantity
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['line_one_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_two_unit_price'].widget.choices= [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_three_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_four_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_five_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_six_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_seven_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_eight_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_nine_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]
self.fields['line_ten_unit_price'].widget.choices = [(i.amount, i.amount) for i in Inventory.objects.all()]

In the queryset attribute inside the ModelChoiceField, you should send pk along with the attribute you want to display. For example: