I built the class Restaurant where you can find the 'def getAmountRating(self):' function. This function should be used as a keyword in my django_filter.
class Restaurant(models.Model):
restaurant_picture = models.ImageField(null=True, default='dashboard-BG.jpg')
name = models.CharField(max_length=200, null=True)
address = models.CharField(max_length=128, blank=True)
houseNumber = models.IntegerField(default=1)
city = models.CharField(max_length=64, default="")
state = models.CharField(max_length=64, default="")
zip_code = models.CharField(max_length=5, default="86444")
tags = models.ManyToManyField(Tag)
affordability = models.FloatField(validators=[MaxValueValidator(3), MinValueValidator(1)], null=True)
objects = models.Manager()
def getAverageRating(self):
comments = Comment.objects.all()
avg = 0
count = 0
for i in comments:
if i.restaurant == self:
avg += i.ratings
if count is 0:
count += 1
else:
avg = avg / 2
if avg is not 0:
avg = round(avg)
return avg
In the filter
class RestaurantFilter(django_filters.FilterSet):
rating = NumberFilter(field_name="getAverageRating", lookup_expr="gte")
class Meta:
model = Restaurant
fields = '__all__'
exclude = ['location', 'restaurant_picture', 'address', 'houseNumber', 'state']
I already had to accept that this is not working as I'd like to.
As a fix I looked up some solutions but those solutions did not work. For example I built an other class containing the def getAverageRating function and referenced this class in Restaurant as a - averageRating = RestaurantRating() - which did not work.
I don't think its relevant but here is the views.py
def restaurants(request):
restaurants = Restaurant.objects.all()
foods = Food.objects.all()
comments = Comment.objects.all()
myFilter = RestaurantFilter(request.GET, queryset=restaurants)
restaurants = myFilter.qs
context = {'restaurants': restaurants, 'myFilter': myFilter, 'foods': foods, 'comments': comments}
return render(request, 'accounts/restaurants.html', context)
I hope someone finds the time to help me with my issue. Thx in advande.
You can not filter, annotate or aggregate on properties or methods. Properties and methods are defined on the Python/Django model, but the database does not know anything about these. Since Queryset
s are translated into database queries, using a method, property does not make much sense.
It would not be efficient anyway if it was possible. It would mean you fetch all the Restaurant
s in memory and then you filter on each restaurant at the Python/Django level. Python is not ideal to filter huge amounts of data, databases are designed to do this.
We however do not need this. A database can determine the average of a related field itself. It has aggregates and it can group by to obtain the average per restaurant. In SQL you would construct a query like:
SELECT restaurant.*, AVG(comment.rating)
FROM restaurant
LEFT OUTER JOIN comment ON comment.restaurant_id = restaurant.id
GROUP BY restaurant.id
you can do this with Django as well. Indeed, we can .annotate(…)
[Django-doc] the restaurants with the average rating of the restaurants with:
from django.db.models import Avg
Restaurant.objects.annotate(
avg_rating=Avg('comment__ratings')
)
We can use this in our FilterSet
with:
class RestaurantFilter(django_filters.FilterSet):
rating = NumberFilter(field_name='avg_rating', lookup_expr='gte')
class Meta:
model = Restaurant
exclude = [
'location'
, 'restaurant_picture'
, 'address'
, 'houseNumber'
, 'state'
]
and in the view we thus can filter with:
from django.db.models import Avg
def restaurants(request):
restaurants = Restaurant.objects.annotate(
avg_rating=Avg('comment__ratings')
)
foods = Food.objects.all()
comments = Comment.objects.all()
myFilter = RestaurantFilter(request.GET, queryset=restaurants)
restaurants = myFilter.qs
context = {'restaurants': restaurants, 'myFilter': myFilter, 'foods': foods, 'comments': comments}
return render(request, 'accounts/restaurants.html', context)