Jack Evans
Jack Evans

Reputation: 25

Django order by integer value, not string value of choices field

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

Answers (1)

Shang Wang
Shang Wang

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

Related Questions