Django how get all invoices containing item

Question:

I spent over two days looking in docs and internet and cannont find solution. I have models:

class Invoice(models.Model):
(...)

class Product(models.Model):
(...)

class InvoicedItems(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=CASCADE)
    article = models.ForeignKey(Product, on_delete=CASCADE)

How to get list of all invoices containing one product? I want to make search engine.

I tried to define in InvoicedItems:

    def get_invoices_by_article(article):
        inv_obj = InvoicedItems.objects.filter(article=article)
        inv = inv_obj.invoice
        return inv

But all the time I get error:
'QuerySet' object has no attribute 'invoice'

I know that I am close but I need your help.
Thanks in advance!

Asked By: user3544781

||

Answers:

So first let answer your question, let’s do some edit to make everything simplier:

in your model add the related_name to your fields:

class InvoicedItems(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=CASCADE, related_name='invoiced_items')
    article = models.ForeignKey(Product, on_delete=CASCADE, related_name='invoiced_items')

Then your query will look like this:

invoices = Invoice.objects.filter(invoiced_items__article=your_article)

Now let’s make it simplier again removing your custom class which right now is useless:

class Product(models.Model):
    [...]

class Invoice(models.Model):
    [...]
    products = models.ManyToManyField(to=Product, related_name='invoices')

And remove your model InvoicedItems.

Then your request become:

invoices = Invoice.objects.filter(products=product)

more info on ManyToManyFields on the documentation

Answered By: Tartempion34

Your problem is this: 'QuerySet' object has no attribute 'invoice'

When you do any .filter() call it always returns a QuerySet Obj.. or a fancy list/array

Outputting in python manage.py shell demonstrates this:

inv_obj = InvoicedItems.objects.filter(article=article_obj)
print(inv_obj)
# <QuerySet [<Article Object (1)>]>
#    ^ Not an Article Object

So your method will work with some minor changes (and a variable name change)

def get_invoices_by_article(article):
    inv_list = InvoicedItems.objects.filter(article=article)
    inv = inv_list.first().invoice
    return inv

but! this raises 2 major issues:

  1. What if there’s no matching articles?
  2. What if there’s multiple?

Example Solutions:

def get_invoices_by_article(article):
    inv_list = InvoicedItems.objects.filter(article=article)

    count = inv_list.count() 

    if count == 0:
        return None

    if count > 1:
        # return list of invoices
        return [i.invoice for i in inv_list]

    # implied: is 1
    # return single invoice
    return inv_list.first().invoice


# Maybe it's easier to ALWAYS return a List, so it's always a single type!
#   empty or not!
def get_invoices_by_article(article):
    return [i.invoice for i in InvoicedItems.objects.filter(article=article)]

After you figure out which path you want to take with the method, I’d recommend
making it into a Model Manager function instead.

Current way:

invoice_item = InvoicedItems.objects.all().first()
my_return = invoice_item.get_invoices_by_article(article_obj)

You must fetch an InvoiceItem Obj before you can use it -> Extra work!

Model Manager way:

my_return = InvoicedItems.objects.get_invoices_by_article(article_obj)

You cut out that db hit!

Basic Model Manager:
from django.db import models

class InvoicedItemsManager(models.Manager):
  def get_invoices_by_article(article):
      return [i.invoice for i in  InvoicedItems.objects.filter(article=article)]


class InvoicedItems(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=CASCADE)
    article = models.ForeignKey(Product, on_delete=CASCADE)

    objects = InvoicedItemsManager()

Simple as that! Now you can add any number of methods you want for InvoicedItems.objects.


But maybe a Model Method or a Manager Method is overkill- idk! It’s up to you

Answered By: Nealium

I made it in this way:

    def get_invoices_by_article(article):
        inv_obj = InvoicedItems.objects.filter(article=article)
        return Invoice.objects.filter(invoiceditems__in=inv_obj)
Answered By: user3544781
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.