Santhosh
Santhosh

Reputation: 11788

Django: while displaying list of articles show favourite status of each articles based on the user

I have List of articles to be displayed as per date. Every User can mark any article as his favourite.

If a user is logged in, then he should be able to see some indication whether the article is already marked as his favourite or not.

the following are my models:

class Favourite(models.Model):
    user = models.ForeignKey(User,on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)
    article = models.ForeignKey(Article, null=True,on_delete=models.CASCADE)
    class Meta:
        unique_together = ('user', 'article')

class Article(models.Model):
    title = models.CharField(max_length=256)
    intro_image = models.ImageField(null=True, blank=True, upload_to=doc_hash)
    description = models.TextField(null=True, blank=True)

I was thinking for every article get all the user ids who marked that article as favourite and then later check it at front end with the current user.

But if some article is marked as favourite by 1000 users , then unneccessarily i will have to get the data of 1000 users along with that article which is too much.

Is there a way if i pass the user i can get the favourite data only with respect to that user for each article so that i can save both on queries and amount of data to be passed to front end.

Upvotes: 0

Views: 379

Answers (3)

Santhosh
Santhosh

Reputation: 11788

I found the below answer from: https://stackoverflow.com/a/51889455/2897115. Using annotations and subquery. Django can be faster using annotations and subquery which i read from. https://medium.com/@hansonkd/the-dramatic-benefits-of-django-subqueries-and-annotations-4195e0dafb16

I am putting the solution given in https://stackoverflow.com/a/51889455/2897115. Using annotations

qs = Post.objects.all()
sub = Like.objects.filter(post__id=OuterRef('pk'), user=request.user)

values = qs.annotate(
    date=Func(F('date'), function='db_specific_date_formatting_func'),
    current_user_like=Exists(sub)
).values('text, 'date', 'current_user_like')

Upvotes: 0

AKX
AKX

Reputation: 169051

Either do that in your view's context or as a custom template tag.

The examples below are dry-coded, so they might have silly bugs.

In a view (assuming a class-based view):

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['is_favourite'] = Favourite.objects.filter(user=self.request.user, article=self.object).exists()
    return context

Usage:

{% if is_favourite %}Yay, you like it!{% endif %}

or a template tag:

@register.simple_tag(takes_context=True)
def is_favourite(context, article):
    request = context['request']
    return Favourite.objects.filter(user=request.user, article=article).exists()

Usage:

{% is_favourite article as fav %}
{% if fav %}Yay, you like it!{% endif %}

Edit

For a ListView, you can do something like

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['favourited_ids'] = set(Favourite.objects.filter(user=self.request.user, article__in=context['object_list']).values_list('article_id', flat=True))
    return context

and use it

 {% if article.id in favourited_ids %}Yay, you like this article!{% endif %}

Upvotes: 1

Arghya Saha
Arghya Saha

Reputation: 5713

I assume that you need a flag to state if the user has already marked the article as favourite or not, suppose there are 100 articles and out of that a user has marked 40 articles as favourite then when the data would be sent then 100 articles data is sent with 40 articles having flag as read TRUE and rest as FALSE.

Following is the SQL equivalent, which you can convert to Django ORM as per your need. 'xyz' is the user id for which you need to show the articles

SELECT article.*, 
       CASE  
           WHEN read.article_id IS NOT NULL then TRUE
           ELSE FALSE
                as read_flag 
from Article as article

left join

   (select article_id from Favourite where user_id = 'xyz') as read 

   on article.id = read.article_id

Upvotes: 0

Related Questions