Home > Software design >  Django how to use annotate with ManyToManyfield?
Django how to use annotate with ManyToManyfield?

Time:11-08

I want to take two fields from my model, one is a float, and the other is a ManyToMany and do an arithmetic operation on them and add them in a field with annotate

At one point it worked for me and then it didn't.

I don't know what would be the correct way I show some things from my files.

models.py

from django.db.models import F, DecimalField
class Tax(models.Model):
    name = models.CharField(max_length=50)
    porcent = models.IntegerField()

    def __str__(self):
        return self.name

class Product(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    code_number = models.IntegerField(verbose_name='Bar Code')
    image = models.ImageField(upload_to='products/')
    purchase_price = models.DecimalField(max_digits=10, decimal_places=2)
    sale_price= models.DecimalField(max_digits=10, decimal_places=2)
    tax = models.ManyToManyField(Tax, blank=True, related_name="tax")
    description = models.TextField()
    created_date = models.DateTimeField(
            default=timezone.now)
    published_date = models.DateTimeField(
            blank=True, null=True)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title

views.py

from django.shortcuts import render
from .models import Tax, Product, Invoice
from django.db.models import F, DecimalField

class InvoiceDashboard(View):
    def get(self, request, *args, **kwargs):
        products=Product.objects.all().annotate(amount_tax = ((F('sale_price') / 100) * F('tax__porcent'))).annotate(
            price_total=(F('sale_price')   F('amount_tax'))
        )

        context = {
            'products': products,
            }
        return render(request, 'pos/cashier.html', context)

pos/cashier.html

{% extends 'pos/base.html' %}

{% block content %}
<tbody id="table_p">
                        {% for product in products %}
                      <tr  id="{{ product.id }}" data-id="{{ product.id }}" data-saleprice="{{ product.sale_price }}" data-codenumber="{{ product.code_number }}" data-amounttax="{{ product.amount_tax|floatformat:2 }}">
                        <th scope="row">{{ product.code_number }}</th>
                        <td><img src="/media/{{ product.image }}" width="60" height="60"/>{{ product.title }}</td>
                        <td><input  id="{{ product.id }}" type="number" value="1" placeholder="1" min="1" max="100" disabled></td>
                        <td  id="{{ product.sale_price }}">{{ product.sale_price }}</td>
                        <td>{% for tax in product.tax.all %}{{ tax }} {% endfor %} | {{ product.amount_tax|floatformat:2 }}</td>
                        <td  id="{{ product.price_total }}">{{ product.price_total|floatformat:2 }}</td>
                        <td >
                            <div >
                            <input  type="checkbox" id="{{ product.id }}">
                            </div>
                        </td>

                      </tr>
                      {% endfor %}
                    </tbody>
{% endblock %}

How can I get the percentage of each of the Taxes in a Product Object?

CodePudding user response:

I would wager a wild guess and say your Tax objects have interger fields without a value.

You haven't specified a default.

Secondly if its not what I said above, because it's a ManyToMany relationship, it doesn't necessarily mean you created any tax objects.

E.g. simply by creating a Product, doesn't create any related Tax objects.

Therefore trying to aggregate or annotate an integer across the ManyToMany will result in a NULL value.

CodePudding user response:

I think the problem with your code above is that in your annotation using tax__porcent, you are not reflecting the fact that their are (potentially) multiple porcent values because it's a many to many relationship between product and tax. So a product won't have single tax_amount, but one for every tax porcent value. Unfortunately, annotations tend to label the calling model, not the related models.

There's two methods I can think of that would handle this. One is to use a through table (docs) for your manytomany. That will enable you to grab one record for each product/tax combination that you can then annotate to your hearts content.

The other method, whch is probably simpler if you want to keep your existing db structure, is to split the annotation process out of the queryset. You can add fields to querysets before sending them to your template, so something like the following might work, as long as you don't requery later:

views.py

    products=Product.objects.all().prefetch_related('tax')
    for product in products:
        for tax in product.tax.all():
             tax.amount_tax = (product.sale_price / 100) * tax.porcent
             tax.price_total = product.sale_price   tax.amount_tax

template.html

<td>
{% for tax in product.tax.all %}
          {{tax}}{{ tax.amount_tax | floatformat:2}} 
{% endfor %} 
</td>
  • Related