Search code examples
djangodjango-querysetrender

django queryset .first() not rendering on html


I am only learning django so any guide would be much appreciated. I am creating an app where authenticated users can like other posts. On one of the pages, I would like to render and show the post with most likes. Please see my code below:

models.py:

class Enter(models.Model):

    title = models.CharField(
        max_length=200, unique=True, null=False, blank=False)
    slug = models.SlugField(
        max_length=200, unique=True, null=False, blank=False)
    competitor = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='post_enter')
    updated_on = models.DateTimeField(auto_now=True)
    content = models.TextField()
    featured_image = CloudinaryField('image', null=False, blank=False)
    created_on = models.DateTimeField(auto_now_add=True)
    status = models.IntegerField(choices=STATUS, default=0)
    likes = models.ManyToManyField(User, related_name='post_likes', blank=True)

    class Meta:
        ordering = ['-created_on']

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Enter, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    def number_of_likes(self):
        return self.likes.count()

views.py:

class CurrentOrderList(generic.ListView):

    model = Enter
    queryset = Enter.objects.filter(status=1).order_by('likes')[0]
    template_name = 'competition.html'

competition.html:

<div class="container-fluid">
    {% for entry in enter_list %}
    <div class="row justify-content-center mt-4">
        <div class="col-md-4">
            <div class="card mb-4">
                <div class="card-body">
                    <div>
                        <img class="card-img-top" src=" {{ entry.featured_image.url }}">
                        <div>
                            <p class="competitor text-muted">By: {{ entry.competitor }}</p>
                        </div>
                    </div>
                    <h2 class="card-title">{{ entry.title }}</h2>
                    <hr />
                    <p class="card-text text-muted h6">{{ entry.created_on}} <i class="far fa-heart"></i>
                        {{ entry.number_of_likes }}</p>
                </div>
            </div>
        </div>
    </div>
    {% endfor %}
</div>

If I remove [0] (or .first()) from the views.py list does get rendered on the page, but some of the items get duplicated - ** using .distinct() does not remove duplicates**

How can I display the entry with highest amount of likes? Thank you!


Solution

  • You probably want to order by the number of likes, that is something else than order by likes. By doing .order_by('-likes'), you order in descending order by the primary key of the liker, and thus the same Enter appears as much as there are likers.

    If you want to order by the number of likes, you work with:

    from django.db.models import Count
    
    
    class CurrentOrderList(generic.ListView):
        model = Enter
        queryset = (
            Enter.objects.filter(status=1)
            .alias(nlikes=Count('likes'))
            .order_by('-nlikes')
        )
        template_name = 'competition.html'

    Using [0] or .first() however makes no sense, then these are not querysets, but Enter objects. The queryset should have a value of type QuerySet, not a model object.

    If you only want to show the most liked post, this is a DetailView, and you thus use get_object instead:

    from django.db.models import Count
    
    
    class CurrentOrderList(generic.DetailView):
        model = Enter
        queryset = (
            Enter.objects.filter(status=1)
            .alias(nlikes=Count('likes'))
            .order_by('-nlikes')
        )
        template_name = 'competition.html'
    
        def get_object(self, *args, **kwargs):
            return self.get_queryset().first()

    then it passes that object as object to the template, so you don't use a {% for … %} to enumerate, you only need to render object.