danirus / django-comments-xtd

A pluggable Django comments application with thread support, follow-up notifications, mail confirmation, like/dislike flags, moderation, a ReactJS plugin and Bootstrap 5.3.
https://django-comments-xtd.readthedocs.io
BSD 2-Clause "Simplified" License
595 stars 158 forks source link

Listing top comments #210

Open bitFez opened 3 years ago

bitFez commented 3 years ago

I've tried implementing this from the view without success and I was wondering if such a feature existed. I want to list top 5 comments for example on an object.

This clearly didn't work :-( top_Comments = django_comment_flags.objects.filter(flag=="I like it").filter(flag.count>=1)

danirus commented 3 years ago

It's not implemented in the app, but it would be a nice to have feature. I will try to draft a possible solution later today. So far I can only confirm that it's not easy.

bitFez commented 3 years ago

Thank you. I'll be looking out for your reply :-)

danirus commented 3 years ago

I can't come up with any medium cost solution. The fact that I use CommentFlag for the like/dislike reactions complicates it a lot. Maybe having a OneToOne relation with another model to keep counters updated asynchronously would be a solution. I will think about it. There have been already many issues reported on how to ease this sort of queries. If you have another idea in mind, please share it, having a few ideas will help to figure the solution.

RealBrionac commented 3 years ago

Quick question Danirus: Why did you decide to use the CommentFlag system for the likes and dislikes?

It might be a stupid idea, but what is the issue with simply having a field in the Comment model containing all the users who liked/disliked the comment. It seems to me that it would make all these filtering much easier.

Is it too slow maybe?

Let me know what you think ^^

danirus commented 3 years ago

The limitation from using CommentFlag was known since the beginning. At the time I started developing this application database engines didn't have support for JSON fields or they were not queryable. With Django 3.1 that has changed, and it can be implemented in a different way. Backward compatibility has to be considered too. Using a JSONField in XtdComment is a good option. Another option is to use a OneToOne relationship to a Reaction model, to keep the number of likes/dislikes along with additional reactions. The list of users associated to each reaction is also a must have.

The operation that happens the most is to retrieve the comments and its reactions. Ideally that would have to happen without making SQL JOINs to consume as less DB resources as possible. Posting reactions happens less often but it is significant and should happen without complicated queries too.

This issue is a top priority. Please, don't hesitate to post your ideas and concerns about it. I'll be glad to read and consider them.

RealBrionac commented 3 years ago

I see. I will try to implement a clean OneToOne relationship since I might need other reactions in the future. I'll tell you how it goes.

Thanks a lot for the library btw, it's great!

Edit: Actually wouldn't it be even better to user ManyToMany relationships with the user base? Something like this:

class Reaction(models.Model):
    comment = models.OneToOneField(XtdComment, unique = True, related_name = 'reactions', on_delete=models.CASCADE)
    likers = models.ManyToManyField(User, related_name = 'likers')
    dislikers = models.ManyToManyField(User, related_name = 'dislikers')
    flaggers = models.ManyToManyField(User, related_name = 'flaggers')

    def __str__():
        self.comment.name + ' reactions'
danirus commented 3 years ago

I prefer a different model to cover slightly different requirements:

The Model I have in mind is this one:

class CommentReaction(models.Model):
    reaction = models.CharField(_('reaction'), max_length=30, db_index=True)
    comment = models.ForeignKey(XtdComment,
                                verbose_name=_('reactions'),
                                related_name="reactions",
                                on_delete=models.CASCADE)
    counter = models.IntegerField(default=0)
    authors = models.ManyToManyField(settings.AUTH_USER_MODEL)

    # Constants for reaction types.
    LIKED_IT = "I liked it"
    DISLIKED_IT = "I disliked it"

I have pushed this changes to the branch issue-210, check the details in this diff. The like and dislike controllers in the views.py module do work already. Can be tested with the comp example project, with the quotes app.

@RealBrionac, are you working on a fix to this issue, planning to send a PR? :-)

RealBrionac commented 3 years ago

@danirus I'm in the process of adapting the library to fit the need of my current application (mainly adding ajax functions to make everything dynamic with no page refresh, and changing the models). Once it's ready and clean I can upload it on another repo , or try to make it backward compatible and send you a PR

ogurec-ogurec commented 3 years ago

please tell, is it possible to make all comments to the object (Post, for example) show already in ascending order from the most popular (from most pluses to the smallest)? @danirus

danirus commented 3 years ago

With version 2.8 is not possible. With v3.0 it is, but it's not ready yet, you could play with it in the branch rel-3.0.0. There is a new CommentReaction model that is better for aggregation (as described here). With v3.0 it's possible to do something like this:

from django.db.models import Q
from django_comments_xtd.models import ReactionEnum, XtdComment

cond = Count('reactions', filter=Q(reactions__reaction=ReactionEnum.LIKE_IT))
queryset = XtdComment.objects.annotate(num_likes=cond).order_by('-num_likes')

That queryset will give you a descending list of comments by the number of likes a comment has received.

I'm sorry it's not done yet. On top of my work load the corona crisis is not helping. I'll get there with a little luck no later than the end of April.

ogurec-ogurec commented 3 years ago

Thanks, we'll wait :) Good luck with your affairs! @danirus

danirus commented 2 years ago

Just for the record, to get the 5 comments with more "Like" reactions posted to a given content_type=16 (it corresponds to content_type.app_label="stories", content_type.model="story") , using the demo project demos/project-stories (set it up following instruction in its README.md file), as it is already implemented in the branch rel-3.0.0, you could do the following from within the project-stories/project_stories directory, with the virtualenv psenv activated:

$(psenv) project_stories $ python manage.py shell

>>> from django_comments_xtd.models import CommentReaction
>>> from project_stories.enums import ReactionEnum   
>>> [
    (obj.comment.id, obj.counter) for obj in CommentReaction.objects.filter(
        reaction=ReactionEnum.LIKE_IT, comment__content_type=16, comment__object_pk=3
    ).order_by('-counter', 'comment__level')[:5]
]
[(881, 6), (899, 5), (890, 5), (868, 4), (866, 3)]

There might be many comments with at least 3 LIKE_IT reactions, so in order to consider first those comments with less nesting level I add 'comment__level' to the order_by clause.

If you run the project-stories demo, you can check that those comments (881, 899, 890, 868 and 866), posted to the story "Colonies in space may be only hope, says Hawking", are the ones that have received more LIKE_IT reactions.