0

In Django, how can I sort the results of a method on my model?

class Flashcard(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE)
    deck = models.ForeignKey(Deck, on_delete=models.CASCADE)
    question = models.TextField()
    answer = models.TextField()
    difficulty = models.FloatField(default=2.5)

    objects = FlashcardManager()

    def __str__(self):
        return self.question

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='profile')
    bio = models.TextField(max_length=500,null=True, default='',blank=True)

    def __str__(self):
        return f'{self.user.username} Profile'

    def avg_diff_user(self):
        avg_diff = Flashcard.objects.filter(owner = self.user).aggregate(Avg('difficulty'))['difficulty__avg']
        return avg_diff

So with avg_diff_user, I get each user's average difficulty rating. Which I can then use in my leaderboard template as follows:

<ol>
   {% for user in leaderboard_list %}
   <li>{{user.username}}:  {{user.profile.avg_diff_user|floatformat:2}}</li>
   {% endfor %}
 </ol>

The results show, but it's not sorted - how can I sort by avg_diff_user? I've read many similar questions on SO, but to no avail. I've tried a different method on my model:

def avg_diff_sorted(self):
    avg_diff_sorted = Flashcard.objects.all().annotate(get_avg_diff_user=Avg(Flashcard('difficulty'))['difficulty__avg'].order_by(get_avg_diff_user))
    return avg_diff_sorted

Which I don't think is right and didn't return any results in my template. I also tried the following, as suggested in https://stackoverflow.com/a/930894/13290801, which didn't work for me:

def avg_diff_sorted(self):
        avg_diff_sorted = sorted(Flashcard.objects.all(), key = lambda p: p.avg_diff)
        return avg_diff_sorted

My views:

class LeaderboardView(ListView):
    model = User
    template_name = 'accounts/leaderboard.html'
    context_object_name = 'leaderboard_list'

    def get_queryset(self):
        return self.model.objects.all()
MeL
  • 1,269
  • 3
  • 12
  • 30
  • in the first code-snippet you've tried for sorting try with `.order_by(avg_diff_user)` instead of `.order_by(get_avg_diff_user)` – Sowjanya R Bhat Jun 03 '20 at 08:51
  • @Sowjanya R Bhat, that doesn't work either – MeL Jun 03 '20 at 09:01
  • second code snippet try `p.avg_diff_user` instead of `p.avg_diff` . i have no idea, just trial-error – Sowjanya R Bhat Jun 03 '20 at 09:08
  • since there is no property as `avg_diff` in the object - i think that key is the mistake . the key should be `avg_diff_user` whatever the code you use . – Sowjanya R Bhat Jun 03 '20 at 09:10
  • @Sowjanya R Bhat no that doesn't work either. Those methods don't seem to be working, because nothing is even showing in the template. – MeL Jun 03 '20 at 09:12
  • so in your template you cannot see the values for `{{user.profile.avg_diff_user|floatformat:2}}` ?? – Sowjanya R Bhat Jun 03 '20 at 09:13
  • @Sowjanya R Bhat I can see them. I meant the other methods, i.e. 'avg_diff_sorted'. avg_diff_user works, it shows the correct values, just not sorted. The other methods do not show anything. – MeL Jun 03 '20 at 09:16
  • ok. where are you declaring `avg_diff_sorted` ? Please add full code you have in `views.py ` that is responsible for loading the `template` that you have shown – Sowjanya R Bhat Jun 03 '20 at 09:24
  • @Sowjana R Bhat, I just realised I put the method in the wrong model (Flashcard instead of Profile). But it's still not working. I'll update my question and include views. – MeL Jun 03 '20 at 09:30

1 Answers1

0

something like:

leaderboard_list = User.objects.all().annotate(avg_score=Avg('flashcard__difficulty').order_by('-avg_score')

will sort you the users by their average score.

I don't use ListView that often by if you just used a standard view like:

def LeaderboardView(request):
    leaderboard_list = ...
    context = {'leaderboard_list':leaderboard_list}
    return render(request, 'accounts/leaderboard.html', context)

In your html you could do the same:

{% for user in leaderboard_list %}
...
{% endfor %}
Ethan
  • 311
  • 3
  • 8
  • what was the error? I'm pretty sure the query is right just need a small fix I guess. Maybe you need to import Avg with: from django.db.models import Avg. I always forget to do that – Ethan Jun 03 '20 at 21:46
  • It just says invalid syntax, pointing to the line: context = {'leaderboard_list':leaderboard_list}. Which is weird, because it looks fine. I did import Avg. (Fyi, it wasn't me who downvoted your answer) – MeL Jun 03 '20 at 23:34
  • haha no worries. and u obvs put the query in leaderboard_list instead of ... right – Ethan Jun 03 '20 at 23:55
  • Yup, I used leaderboard_list = User.objects.all().annotate(avg_score=Avg('flashcard__difficulty').order_by('-avg_score') – MeL Jun 03 '20 at 23:57
  • maybe the tabs are off when u copy pasted? – Ethan Jun 04 '20 at 00:09
  • I found the error - there should be another comma after Avg('flashcard__difficulty'). Now it's working - thanks! – MeL Jun 04 '20 at 01:00
  • ahh my bad makes sense – Ethan Jun 04 '20 at 01:10