Reputation: 25
I'm having some trouble with the Django choices field. I have written code to calculate the average rating of the last 5 results for each StudySpace model and store these ratings as Strings i.e. "Busy", "Quite Busy", etc etc.
I'm able to do this no problem. However, I want to be able to sort StudySpaces in the index page, but by their average Rating (as the old integer value).
I've tried
study_space_list = StudySpace.objects.order_by('avg_rating')
But when I do this records are sorted alphabetically shown here, whereas ideally I'd like them to be sorted using the corresponding numerical values in the choices tuple. i.e Busy: 1, Empty: 5.
The Ratings model, along with corresponding choices looks as follows
class Rating(models.Model):
studyspace = models.ForeignKey(StudySpace, on_delete=models.CASCADE)
BUSY = 1
QUITE_BUSY = 2
AVERAGE = 3
QUITE_EMPTY = 4
EMPTY = 5
rating_choices = (
(BUSY, 'Busy'),
(QUITE_BUSY, 'Quite Busy'),
(AVERAGE, 'Average'),
(QUITE_EMPTY, 'Quite Empty'),
(EMPTY, 'Empty'),
)
rating = models.IntegerField(choices=rating_choices, default=AVERAGE)
The simplified view in which I wish to order StudySpaces by their average score looks as follows
def index(request):
study_space_list = StudySpace.objects.order_by(<<<NO CLUE WHAT TO DO HERE>>>)
context = {'study_space_list': study_space_list}
return render(request, 'spacefinder/index.html', context)
The average Rating is calculated by the StudySpace on each update
class StudySpace(models.Model):
department = models.ForeignKey(Department, on_delete=models.CASCADE)
space_name = models.CharField(max_length=200, unique=True)
avg_rating = models.CharField(default='Average', max_length=20)
def save(self, *args, **kwargs):
"""Recalculates avg_rating on save"""
query_set = Rating.objects.filter(studyspace=self.id).order_by('-id')[:10]
if query_set.exists():
average = query_set.aggregate(Avg('rating')).get('rating__avg')
self.avg_rating = get_busyness_score(average)
super(StudySpace, self).save(*args, **kwargs)
def get_busyness_score(value):
return Rating.rating_choices[round(value)-1][1]
Upvotes: 0
Views: 1818
Reputation: 25539
You shouldn't rely on changing database data to adapt to the way you want to display in the template. If you sort on a CharField
that contains integer strings, it would only sort on lexicographic order, which is not what you want, like 21
is smaller than 3
because 3
is larger by comparing the first digit.
The solution is not that hard, just change avg_rating
to IntegerField
or better, DecimalField
, which is specialized in dealing with human readable numbers. If you want to add something fancy in template, you could always write a custom template tag to show extra stuff around the numbers.
Read python doc about what is Decimal, and read django doc about how to create custom template tag.
Upvotes: 1