0

I have two models that are located in two different apps; Invoice and Inventory.

InvoiceModel:

class Invoice(models.Model):
    line_one = models.ForeignKey(Inventory, to_field='title', on_delete=models.CASCADE, default='', blank=True, null=True, related_name='+', verbose_name="Line 1")
    line_one_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
    line_one_quantity = models.IntegerField('Quantity', 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.title

InventoryModel:

class Inventory(models.Model):
  product_number = models.IntegerField(blank=True, null=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

So basically the user adds a product using the Inventory model and then in the Invoice model, they'll have a drop-down menu for line_one and when they click a certain item, I want the line_one_unit_price to get populated with the price of the item selected in the line_one option!

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()]

By using the above logic, I can list the amount of all products present in the Inventory but I want it to only display the amount of the product that the user has selected in line_one.

Screenshot: screenshot

In the image, the amount of the first product is automatically added but instead, I want it to be "-----" by default. How can I implement that? Thank you.

perpetualdarkness
  • 135
  • 1
  • 4
  • 18
  • It's a little strange that your `Invoice.line_one` is a foreign key to the `title` instead of the primary key. If you ever need to change the name of an `Inventory` item then you'll need to update every invoice. – Tim Tisdall Jul 05 '22 at 12:39
  • @TimTisdall Sorry. I'm new. If `Invoice.line_one` is a foreign key to the `product.number` i.e., the primary key then `line_one` will have a dropdown menu of product numbers which isn't what I want. So I thought that relationship made more sense. – perpetualdarkness Jul 05 '22 at 13:02
  • Every table has a default `id` primary key. If you remove the `to_field='title'` then it'll default to using the primary key. If you want `product_number` to be the primary key then you need to add a `primary_key=True` to that field, otherwise the default `id` field will be used as the primary key. To fix the display, you should use a `ModelChoiceField` and specify which thing to display. – Tim Tisdall Jul 05 '22 at 13:15
  • I did use `ModelChoiceField` once but it was just when I had a few choices in mind but in this case, I want the list of objects to act like a choicelist and I'm not sure how I can do that? Thank you – perpetualdarkness Jul 06 '22 at 02:21
  • You still are using `ModelChoiceField` for `line_one` as that's the default and you haven't overridden it. I'm saying you need a `line_one = forms.ModelChoiceField()` with additional settings to get it to display correctly instead of using `to_field`. Don't modify your database schema purely to make the form look correct, you can always modify the form. – Tim Tisdall Jul 06 '22 at 15:03
  • @TimTisdall Thank you! You're right. I am now displaying the lines using the `ModelChoiceField` and also gave `product_number` as the primary key. Can you please tell me the name of the concept I am searching for based on my current question? i.e., to want the appropriate price displayed based on the object? So that I can search more about it on the internet. – perpetualdarkness Jul 06 '22 at 16:10
  • @TimTisdall After referencing the foreign ley, I am getting this, `ValueError at /invoice/add_invoice/ Cannot assign "'Asus T'": "Invoice.line_one" must be a "Inventory" instance.` T-T – perpetualdarkness Jul 06 '22 at 16:55
  • If you want one field to dynamically change whenever you change another, my answer below covers that. – Tim Tisdall Jul 07 '22 at 12:28
  • @TimTisdall I tried this code: `def add_invoice(request): form = InvoiceForm(request.POST or None) data = Inventory.objects.all() dict_obj = model_to_dict(data) serialized = json.dumps(dict_obj) #... if form.is_valid(): form.save() return redirect('/invoice/list_invoice') context = { "form": form, "serialized":serialized, } return render(request, "entry.html", context)` Is that the correct way? I am not sure how I can access that JSON object in javascript? – perpetualdarkness Jul 07 '22 at 16:42
  • Sorry, this question is now getting out of control... If you run into specific problems, you can try posting new questions. I will say that you now have the data in a string, so you can do `my_data = {{ serialized }}` in the template (within a ` – Tim Tisdall Jul 08 '22 at 12:26
  • @TimTisdall I am getting this error - `AttributeError at /invoice/add_invoice/ 'QuerySet' object has no attribute '_meta'`. Please tell me how to debug this and I'll stop asking any more questions. Thank you for the support. – perpetualdarkness Jul 08 '22 at 13:13

1 Answers1

0

There's different ways you can do this, but essentially there's no built-in simple way. You need to employ JavaScript on the front-end to detect when that "Line 1" field changes and then fill in the "Unit Price" accordingly.

If you don't have too many "Line 1" items, one way of doing it is creating your own widget to embed the price in each <option> of the drop-down with a data- attribute. Then you include some JavaScript that detects when that drop-down has changed and updates the read-only "Unit Price" field. On the back-end, you should also make sure you don't use that form field and look-up the price based on the "Line 1" field.

Another option is to make an AJAX call to an endpoint to fetch the price.

EDIT: If there's multiple drop-downs with the same list of items, then having a global JS variable with a price look-up might work best (if not too large). Generate a look-up dict of product_number to amount (price), pass that to the template to output as JSON in a global variable, and then use that in your JavaScript to populate the "Unit Price".

Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
  • I have a total of 10 Lines! Can you please share a link to the documentation or anything really that shows how to fill the Unit Price accordingly? – perpetualdarkness Jul 05 '22 at 12:30
  • Can you please check this? https://stackoverflow.com/questions/72926096/attributeerror-at-invoice-add-invoice-int-object-has-no-attribute-meta-wh Thanks – perpetualdarkness Jul 10 '22 at 05:29