calculating number of like and dislike

Hi
My app have these three models

users

class Customers(models.Model):
    customer_id = models.PositiveIntegerField(unique=True, verbose_name='کد مشتری')
    f_name = models.CharField(verbose_name='نام')
    l_name = models.CharField(verbose_name='نام خانوادگی')
    national_id = models.CharField(
        validators=[RegexValidator('[0-9]{10}', message='کدملی معتبر نیست')], max_length=10, verbose_name='کدملی')
    state_code = models.ForeignKey(StateCodes, on_delete=models.CASCADE, verbose_name='استان')
    city = models.CharField(verbose_name='شهر')
    address = models.CharField(verbose_name='آدرس')
    postal_code = models.CharField(
        validators=[RegexValidator('[0-9]{10}', message='کدپستی معتبر نیست')], max_length=10, verbose_name='کدپستی')
    email = models.EmailField(unique=True, verbose_name='آدرس ایمیل')
    cellphone_number = models.CharField(
        validators=[RegexValidator('[0][9][0-9]{9}', message='شماره تلفن معتبر نیست')],
        max_length=11, verbose_name='تلفن همراه')

comments

class Comments(models.Model):
    goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='کد کالا', related_name='comments')
    customer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='کد مشتری')
    text = models.TextField(verbose_name='متن نظر', help_text='متن نظر را وارد کنید')
    date = models.DateTimeField(auto_now=True, verbose_name='تاریخ ثبت رای')

and vote

class Vote(models.Model):
    comment = models.ForeignKey(Comments, on_delete=models.CASCADE, null=True, blank=True,
                                related_name='comment_vote', verbose_name='نظر مربوطه')
    customer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True,
                                 related_name='comment_user', verbose_name='کاربر نظر داده')
    type = models.BooleanField(verbose_name='نوع نظر')
    voted_customer = models.ManyToManyField(CustomUser, verbose_name='کاربران رای داده',
                                            related_name='comment_likes')

i managed to capture customer votes(like or dislike ) for a certain comment of a product
the thing that i want to do now is to calculate number of likes and dislike of any comment about any product how could i do that?

i defined several queries to gain that but no success

any help would be appreciated.

In your Comments Model you can create a property something like

class Comments(models.Model):
    ....

    @property
    def get_votes(self):
        count = self.comment_vote.all().count()
        return count

here self.comment_vote will give you all the objects from Vote Model for a particular comment ForeignKey with comment_vote related_name that you have given in your field.

thanks for your reply
but i was looking for a solution using queryset

Your original post didn’t provide any context for which you want to calculate this value.

What is the base query that you want to do this for? (Or, to phrase this differently, where do you want this done?)

I designed a page that displays a product specifications also any logged in customer could leaves s comment and other customers could voto to that comment (like or dislike)
I want the calculation at this page beside the comment (something like YouTube like and dislike for any video)
I could post view and templates if you want

Yes, we need to see where you’re starting from to be able to provide a concrete answer to your question. (Just the view, we don’t need to see the template.)

this is the view file an where i want to calculate votes
views.py

class GoodsListView(DetailView):
    model = Goods
    template_name = 'goods_list.html'

    """
           we gather all kind of details about a specific good in goods_detail method such as comment and person
           who leaves it for that good,a brif intro and criticizes about a product  
    """

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['detail'] = Goods.objects.filter(id=self.kwargs['pk']).values()
        context['comment'] = Comments.objects.filter(goods=self.kwargs['pk']). \
            values('id', 'goods', 'customer', 'text').order_by('customer')
        context['customer'] = CustomUser.objects.filter(id__in=
        Comments.objects.filter(goods=self.kwargs['pk']).values(
            'customer')) \
            .values('id', 'first_name', 'last_name').order_by('id')
        context['goods_main_band'] = MainBranch.objects.filter(mb_code__in=
        Goods.objects.filter(id=self.kwargs['pk']).values(
            'goods_main_band_id')).values('main_desc')
        context['goods_sub_band1'] = SubBranch1.objects.filter(sb1_code__in=
        Goods.objects.filter(id=self.kwargs['pk']).values(
            'goods_sub_band1_id')).values('sb1_desc')
        context['goods_specification'] = GoodsSpecification.objects.filter(goods_sub_band2__in=
        Goods.objects.filter(
            id=self.kwargs['pk']).values(
            'goods_sub_band2_id')).values(
            'spec_desc')
        context['goods_properties'] = GoodsProperties.objects.filter(goods_sub_band2__in=
        Goods.objects.filter(id=self.kwargs['pk']).values(
            'goods_sub_band2_id')).values('goods_intro', 'goods_criticize')
        context['comment_form'] = CommentForm         
        like_count =  comments.objects.filter(id__in=Comments.objects.filter(goods=self.kwargs['pk']).values('id')
                                             , comment_vote__type=True
                                             , comment_vote__customer__in=CustomUser.objects.filter(id__in=
           Comments.objects.filter(goods=self.kwargs['pk']).values(
                'customer')).values('id')).count()
        dislike_count = Comments.objects.filter(id__in=Comments.objects.filter(goods=self.kwargs['pk']).values('id')
                                                 , comment_vote__type=False,
                                                comment_vote__customer__in=CustomUser.objects.filter(id__in=
                                                 Comments.objects.filter(goods=self.kwargs['pk']).values(
                                                    'customer')).values('id')).count()               
        context['vote_up'] = like_count
        context['vote_down'] = dislike_count
        return context

    def post(self, request, *args, **kwargs):
        view = CommentPost.as_view()
        return view(request, *args, **kwargs)

i manage to count likes and dislike but the problem is the above code calculates all votes of all comment of a specific product i want to group them by each comment
better say i want to calculate likes and dislikes of every comment

So for clarity, these are the two queries where you want to get these counts?

Yes that is right those are the queries

I did something similar here:

class Post(models.Model):

    ...

    def votes(self):
        upvotes = Vote.objects.filter(post=self, up_or_down=True).count()
        downvotes = Vote.objects.filter(post=self, up_or_down=False).count()
        total_votes = upvotes - downvotes
        return total_votes

    ...


class Vote(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    up_or_down = models.BooleanField()

    class Meta:
        verbose_name_plural = 'Votos'
        verbose_name = 'Voto'

I hope it’s helpful to you.

1 Like

This doesn’t seem to fit within the structure that you’re appearing to create for your context.
Since you want these values for each Comment, you would want to calculate it for the comments being retrieved in this query, probably annotating the values to the queryset results:

context['comment'] = Comments.objects.filter(goods=self.kwargs['pk']). \
            values('id', 'goods', 'customer', 'text').order_by('customer')

I’m looking at this trying to understand what it is you’re attempting to do to generate your output. You’re issuing a lot of queries with no apparent relationship among the entries in your context.

What I think would be helpful would be a description of the page you’re trying to build here. This does not mean your template - if your template is attempting to render the data created in this context, I don’t think it’s going to help much. What would be most helpful would be a description of what you want to create, with some information as to what the data is being displayed along with where that data is coming from.

let me explain it for you
actually i’m trying to design a web store so after selecting the product, customer will redirect to a page that displays product info such as photos of it, price,specifications and so on
below to that part any customer could leave a comment about that product
and in every comment there is a place(with two button one for like and one for dislike) for customers to vote about comments of other customers(except him/her self)
now what i want to calculate is number of likes and dislike of every comment
those part above like_count and dislike_count prepare the context to be rendered at the template
hope this explanation clarifies the issue

Your description makes sense, but I’m not understanding how your context is being used to create the page.

For example, you wrote:

That implies to me that you’re looking to create a page with this information for one product, with all the comments for that product.

I guess most of what I’m noticing is that you’re creating a lot of queries for data that can be referenced directly, and you’re using filter where you should be using get, and you’re using values in situations where it’s not needed and not helpful.

For example, this is a detail view, which means that the view itself is going to retrieve the instance of Goods matching the parameter and create the class attribute self.object. That makes this query unnecessary:

Since Comments is related to Goods by the goods ForeignKey field, you don’t need to query for those comments. The expression self.object.comments.all() Will give you the list of all comments related to self.object.

I’m not sure I understand what this expression is doing for you, but the customer attribute on each Comments is a direct reference to the CustomUser objects you’re retrieving here.

I can tell that Vote connects a comment with a customer - in this case, I’m assuming that customer is the person casting that vote on that comment. If that’s correct, what’s the purpose of voted_customer? What purpose does that serve here?

And without seeing the rest of the models, it’s not possible for me to identify the further simplification of your view, but it’s pretty obvious that a lot of this can be simplified.

Thanks for the piece of code you provided
this code calculate all votes(up or down) of all comments of a specific post
but i want to calculate votes of each comment separately.
say it another way i want to group by votes of a post per user

Thanks for your detail analysis of my code
you are right about those part responsible for fetching data to be displayed in product page i will correct them
and you also right about field voted_customer it was for the time that i want to calculate votes(regardless of its type i mean like or dislike) and should be removed i forgot to do that
now as you mentioned the kind of query i am going to make related to three models users,comments and vote so these are three models
users model

class Customers(models.Model):
    customer_id = models.PositiveIntegerField(unique=True, verbose_name='کد مشتری')
    f_name = models.CharField(verbose_name='نام')
    l_name = models.CharField(verbose_name='نام خانوادگی')
    national_id = models.CharField(
        validators=[RegexValidator('[0-9]{10}', message='کدملی معتبر نیست')], max_length=10, verbose_name='کدملی')
    state_code = models.ForeignKey(StateCodes, on_delete=models.CASCADE, verbose_name='استان')
    city = models.CharField(verbose_name='شهر')
    address = models.CharField(verbose_name='آدرس')
    postal_code = models.CharField(
        validators=[RegexValidator('[0-9]{10}', message='کدپستی معتبر نیست')], max_length=10, verbose_name='کدپستی')
    email = models.EmailField(unique=True, verbose_name='آدرس ایمیل')
    cellphone_number = models.CharField(
        validators=[RegexValidator('[0][9][0-9]{9}', message='شماره تلفن معتبر نیست')],
        max_length=11, verbose_name='تلفن همراه')

comments model

class Comments(models.Model):
    goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='کد کالا', related_name='comments')
    customer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name='کد مشتری')
    text = models.TextField(verbose_name='متن نظر', help_text='متن نظر را وارد کنید')
    date = models.DateTimeField(auto_now=True, verbose_name='تاریخ ثبت رای')

and vote model

class Vote(models.Model):
    comment = models.ForeignKey(Comments, on_delete=models.CASCADE, null=True, blank=True,
                                related_name='comment_vote', verbose_name='نظر مربوطه')
    customer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True,
                                 related_name='comment_user', verbose_name='کاربر نظر داده')
    type = models.BooleanField(verbose_name='نوع نظر')  

so how could i write a query to count votes(like or dislike) of each comment per user who leaves comment?

Assuming you’ve refactored your code as described above, these counts can be annotated on your original query.

This means your get_queryset() method in your view class may end up looking something like this:

def get_queryset(self):
    comment_counts = Comments.objects.annotate(
        like_count=Count('comment_vote', filter=Q(comment_vote__type=True)),
        dislike_count=Count('comment_vote', filter=Q(comment_vote__type=False))
    )
    queryset = super().get_queryset()
    queryset.prefetch_related(
        Prefetch('comments', queryset=comment_counts)
    )
    return queryset
)

(Note: I’m winging this based on the Cheat Sheet for annotations and aggregations. I’m not in a position to test this myself, so the syntax may not be correct.)

You can create a custom Manager and then get the count

Well, I have searched about it over the internet and I also found too many results for the same query. Well, I also found this random code, can you try this query once, may be you can get what you are looking for.

from django.db.models import Count

# Assuming you have a specific 'comment_id' for which you want to get the likes and dislikes.
comment_id = 1  # Replace with the actual comment_id

# Query to get the count of likes and dislikes for a specific comment
comment_votes = Vote.objects.filter(comment_id=comment_id)

# Annotate the number of likes
comment_votes = comment_votes.annotate(num_likes=Count('id', filter=models.Q(type=True)))

# Annotate the number of dislikes
comment_votes = comment_votes.annotate(num_dislikes=Count('id', filter=models.Q(type=False)))

# Get the results
result = comment_votes.values('num_likes', 'num_dislikes').first()

# Print the result
print(f"Likes: {result['num_likes']}, Dislikes: {result['num_dislikes']}")

Thanks

Sorry but code in your example is actually bad advice.

  • annotation and count usage is incorrect (or odd at least)

OP should use get_queryset example by @KenWhitesell reply.

Thank you for your solution.it worked