Каков наиболее эффективный способ получения кверисета django с наибольшим количеством постов для связанного имени?
В настоящее время я работаю над сайтом, на котором будут размещаться объявления о продаже и аренде автомобилей. Я хотел бы получить набор запросов, который выделяет только одну марку автомобиля (, т.е. Audi), которая имеет наибольшее количество сообщений для соответствующего model
. Пример:
Отображение марки Audi, поскольку она имеет наибольшее количество связанных сообщений.
Мой вопрос в том, какой самый эффективный способ сделать это? Я проделал некоторую работу здесь, но я уверен, что это не самый эффективный способ. У меня есть следующее:
# Algorithm that is currently retrieving the name of the brand and the number of related posts it has.
def top_brand_ads():
queryset = Advertisement.objects.filter(status__iexact="Published", owner__payment_made="True").order_by('-publish', 'name')
result = {}
for ad in queryset:
# Try to update an existing key-value pair
try:
count = result[ad.brand.name.title()]
result[ad.brand.name.title()] = count + 1
except KeyError:
# If the key doesn't exist then create it
result[ad.brand.name.title()] = 1
# Getting the brand with the highest number of posts from the result dictionary
top_brand = max(result, key=lambda x: result[x]) # Returns for i.e. (Mercedes Benz)
context = {
top_brand: result[top_brand] # Retrieving the value for the top_brand from the result dict.
}
print(context) # {'Mercedes Benz': 7} -> Mercedes Benz has seven (7) related posts.
return context
Есть ли способ вернуть queryset вместо этого, не делая того, что я сделал здесь, или это может быть намного более эффективным?
Если необходимы соответствующие модели, пожалуйста, смотрите ниже:
models.py
# Brand
class Brand(models.Model):
name = models.CharField(max_length=255, unique=True)
image = models.ImageField(upload_to='brand_logos/', null=True, blank=True)
slug = models.SlugField(max_length=250, unique=True)
...
# Methods
# Owner
class Owner(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
telephone = models.CharField(max_length=30, blank=True, null=True)
alternate_telephone = models.CharField(max_length=30, blank=True, null=True)
user_type = models.CharField(max_length=50, blank=True, null=True)
payment_made = models.BooleanField(default=False)
expiring = models.DateTimeField(default=timezone.now)
...
# Methods
# Advertisement (Post)
class Advertisement(models.Model):
STATUS_CHOICES = (
('Draft', 'Draft'),
('Published', 'Published'),
)
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=150, blank=True, null=True)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=True, null=True)
publish = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Draft')
...
# Other fields & methods
Любая помощь будет очень признательна.
Пробовали ли вы использовать метод count?
from django.db.models import Count
Car.objects.annotate(num_views=Count('car_posts_related_name')).order_by('num_views')
Поскольку вам нужны бренды, давайте сделаем запрос по модели бренда:
Brand.objects.filter(advertisement_set__status__iexact="Published").\
filter(advertisement_set__owner__payment_made=True).\
annotate(published_ads=Count('advertisement_set')).\
order_by('-published_ads')
Однако, даже в предложенном вами решении можно немного улучшить:
- Удалите метод
order_by
из вашего набора запросов. Он не влияет на конечный результат, но добавляет некоторые накладные расходы, особенно если ваша модельAdvertisement
не индексирована по этим полям. - Каждый раз, когда вы вызываете
ad.brand
, вы обращаетесь к базе данных. Это называется проблемой N+1. Находясь в цикле из n, вы совершаете n дополнительных обращений к БД. Вы можете использоватьselect_related
, чтобы избежать таких проблем. В вашем случае:Advertisement.objects.select_related('brand')...