Каков оптимальный способ доступа к данным в отношении OneToMany в django для вывода в шаблон?

#python #django #django-models #django-views #django-templates

#python #django #django-модели #django-представления #django-шаблоны

Вопрос:

Я работаю в django, и моя цель — вывести в шаблон таблицу, состоящую из всех моих клиентов и соответствующей суммы, равной общей сумме, которую они потратили.

У меня есть классы / модели Customers и Transactions отношения OneToMany. Каждый раз, когда клиент совершает покупку, транзакция записывается как сумма, которую они потратили ( tx_amount ) . У меня есть следующий набор кода, который работает, но я считаю, что он не является оптимальным, поскольку он выполняется за O (x * y) время, т. Е. Выполняется полный цикл Transactions для каждого клиента.

Q1: Каков оптимальный способ выполнения этой задачи?

Когда я изначально пытался заставить свой шаблон работать, вместо использования local_customer я использовал setattr(customer[x],"value",tx_amount) который работал в оболочке django, но не работал в шаблоне. Моим обходным путем было создание локального класса, который я бы использовал для заполнения моей context переменной.

Q2: При объединении моделей данных для вывода в шаблоне необходимо ли использовать какой-то локальный класс, подобный моей local_customer реализации ниже, или есть лучший способ?

Псевдокод ниже:

models.py:

 class Customers(models.Model):
    name = models.CharField(max_length=70)
    def __str__(self):
        return self.name

class Transactions(models.Model):
    customers = models.ForeignKey(Customers, on_delete=models.CASCADE)
    tx_amount = models.DecimalField(max_digits=9, decimal_places=2
 

views.py:

 class Local_Customer:
    name = ""
    total_spent = 0

    def __str__(self, name):
        self.name = name

def customer_view(request):
    
    customer = Customers.objects.all()
    customer_context = [] # list of objects we'll pass to render

    for x in range(len(customer)):
        local_customer = Local_Customer(customer[x].name)
        customer_txs = Transactions.objects.filter(customer__name=customer[x])

        for y in customer_txs:
            local_customer.total_spent  = y.tx_amount
        customer_context.append(local_customer)

     context = {'customers' : customer_context}

    html_template = loader.get_template( load_template )
    return HttpResponse(html_template.render(context, request))
 

template.html:

 {% for customer in customers %}
    {{customer.name}}
    {{customer.total_spent}}
{% endfor %}
 

Ответ №1:

Вы можете .annotate(…) [Django-doc] сделать это на стороне базы данных:

 from django.db.models import Sum
from django.shortcuts import render

def customer_view(request):
    customers = Customers.objects.annotate(
        total_spent=Sum('transactions__tx_amount')
    )
    context = {'customers' : customers}
    return render(request, load_template, context) 

Таким образом, это сгенерирует запрос, который выглядит как:

 SELECT customers.*, SUM(transactions.tx_amount) AS total_spent
FROM customers
LEFT OUTER JOIN transactions ON transactions.customers_id = customers.id 

и, таким образом, извлекает все данные в одном запросе.

Если транзакций нет, total_spent will be None ( NULL ) , not 0 , вы можете использовать Coalesce [Django-doc] для использования нуля вместо:

 from django.db.models import Sum, Value
from django.db.models.functions import Coalesce
from django.shortcuts import render

def customer_view(request):
    customers = Customers.objects.annotate(
        total_spent=Coalesce(Sum('transactions__tx_amount'), Value(0))
    )
    context = {'customers' : customers}
    return render(request, load_template, context)