rating and comment system complete
This commit is contained in:
@@ -19,11 +19,13 @@ from django.utils.text import slugify
|
||||
from common.markdown import render_markdown
|
||||
from .forms import ContributorForm, ProjectForm, VersionForm
|
||||
from .models import (
|
||||
Comment,
|
||||
FileIndex,
|
||||
Project,
|
||||
ProjectAsset,
|
||||
ProjectContributor,
|
||||
ProjectDraft,
|
||||
Rating,
|
||||
Tag,
|
||||
TagCategory,
|
||||
TagList,
|
||||
@@ -259,6 +261,10 @@ def browse(request):
|
||||
projects = projects.annotate(_downloads=Sum('versions__downloads')).order_by(
|
||||
F('_downloads').desc(nulls_last=True)
|
||||
)
|
||||
elif sort == 'most_liked':
|
||||
projects = projects.order_by('-rating_score')
|
||||
elif sort == 'most_voted':
|
||||
projects = projects.order_by('-rating_count')
|
||||
elif sort == 'name':
|
||||
projects = projects.order_by('title')
|
||||
else:
|
||||
@@ -373,6 +379,21 @@ def project_detail(request, slug):
|
||||
if loader and loader not in mod_loaders:
|
||||
mod_loaders.append(loader)
|
||||
|
||||
# Ratings + forum.
|
||||
rating_agg = project.ratings.aggregate(
|
||||
positive=Count('pk', filter=Q(value=True)),
|
||||
total=Count('pk'),
|
||||
)
|
||||
rating_positive = rating_agg['positive'] or 0
|
||||
rating_total = rating_agg['total'] or 0
|
||||
rating_negative = rating_total - rating_positive
|
||||
rating_percent = round(rating_positive / rating_total * 100) if rating_total else 0
|
||||
user_rating = None
|
||||
if request.user.is_authenticated:
|
||||
user_rating = project.ratings.filter(user=request.user).values_list('value', flat=True).first()
|
||||
comments = project.comments.select_related('user').order_by('-created_at')[:200]
|
||||
can_comment = request.user.is_authenticated
|
||||
|
||||
autoload_plugins_json = ''
|
||||
if plugins_manifest:
|
||||
members = [
|
||||
@@ -397,6 +418,15 @@ def project_detail(request, slug):
|
||||
'mod_manifest': mod_manifest,
|
||||
'mod_loaders': mod_loaders,
|
||||
'mod_count': mod_count,
|
||||
'rating_stats': {
|
||||
'positive': rating_positive,
|
||||
'negative': rating_negative,
|
||||
'total': rating_total,
|
||||
'percent': rating_percent,
|
||||
},
|
||||
'user_rating': user_rating,
|
||||
'comments': comments,
|
||||
'can_comment': can_comment,
|
||||
'autoload_plugins_json': autoload_plugins_json,
|
||||
'anim_stats': anim_stats,
|
||||
}
|
||||
@@ -1311,6 +1341,108 @@ def contributors(request, slug):
|
||||
})
|
||||
|
||||
|
||||
# ---------- Ratings & Forum ----------
|
||||
|
||||
def refresh_rating_fields(project):
|
||||
"""Recompute Project.rating_score (net) + rating_count from its votes."""
|
||||
agg = project.ratings.aggregate(
|
||||
up=Count('pk', filter=Q(value=True)),
|
||||
total=Count('pk'),
|
||||
)
|
||||
up = agg['up'] or 0
|
||||
total = agg['total'] or 0
|
||||
project.rating_count = total
|
||||
project.rating_score = 2 * up - total
|
||||
project.save(update_fields=['rating_score', 'rating_count'])
|
||||
|
||||
|
||||
def _require_rating_user(request, project):
|
||||
"""Redirect guests to login; return None when allowed."""
|
||||
if request.user.is_authenticated:
|
||||
return None
|
||||
return redirect('{}?next={}'.format(
|
||||
reverse('profiles:login'),
|
||||
request.path,
|
||||
))
|
||||
|
||||
|
||||
def rate_project(request, slug):
|
||||
"""Set / change / clear the current user's vote on a project."""
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
if (login_redirect := _require_rating_user(request, project)) is not None:
|
||||
return login_redirect
|
||||
if request.method != 'POST':
|
||||
return redirect('library:project_detail', slug=slug)
|
||||
|
||||
value_raw = request.POST.get('value', '').strip()
|
||||
if value_raw in ('1', 'up'):
|
||||
value = True
|
||||
elif value_raw in ('0', 'down'):
|
||||
value = False
|
||||
else:
|
||||
value = None # clear the vote
|
||||
|
||||
rating, created = Rating.objects.get_or_create(
|
||||
project=project, user=request.user, defaults={'value': value if value is not None else True},
|
||||
)
|
||||
if value is None:
|
||||
rating.delete()
|
||||
messages.success(request, 'Your rating was removed.')
|
||||
elif created or rating.value != value:
|
||||
rating.value = value
|
||||
rating.save(update_fields=['value', 'updated_at'])
|
||||
messages.success(request, 'Your rating was updated.')
|
||||
else:
|
||||
messages.info(request, 'Your rating is unchanged.')
|
||||
refresh_rating_fields(project)
|
||||
return redirect(reverse('library:project_detail', args=[slug]) + '#rating')
|
||||
|
||||
|
||||
def add_comment(request, slug):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
if (login_redirect := _require_rating_user(request, project)) is not None:
|
||||
return login_redirect
|
||||
if request.method == 'POST':
|
||||
body = request.POST.get('body', '').strip()
|
||||
if body:
|
||||
Comment.objects.create(project=project, user=request.user, body=body)
|
||||
messages.success(request, 'Comment added.')
|
||||
else:
|
||||
messages.error(request, 'Comment cannot be empty.')
|
||||
return redirect(reverse('library:project_detail', args=[slug]) + '#forum')
|
||||
|
||||
|
||||
def edit_comment(request, slug, comment_id):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
if (login_redirect := _require_rating_user(request, project)) is not None:
|
||||
return login_redirect
|
||||
comment = get_object_or_404(Comment, pk=comment_id, project=project)
|
||||
if comment.user_id != request.user.pk and not request.user.is_staff:
|
||||
return HttpResponseForbidden('You can only edit your own comments.')
|
||||
if request.method == 'POST':
|
||||
body = request.POST.get('body', '').strip()
|
||||
if body:
|
||||
comment.body = body
|
||||
comment.save(update_fields=['body', 'updated_at'])
|
||||
messages.success(request, 'Comment updated.')
|
||||
else:
|
||||
messages.error(request, 'Comment cannot be empty.')
|
||||
return redirect(reverse('library:project_detail', args=[slug]) + '#forum')
|
||||
|
||||
|
||||
def delete_comment(request, slug, comment_id):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
if (login_redirect := _require_rating_user(request, project)) is not None:
|
||||
return login_redirect
|
||||
comment = get_object_or_404(Comment, pk=comment_id, project=project)
|
||||
if comment.user_id != request.user.pk and not request.user.is_staff:
|
||||
return HttpResponseForbidden('You can only delete your own comments.')
|
||||
if request.method == 'POST':
|
||||
comment.delete()
|
||||
messages.success(request, 'Comment deleted.')
|
||||
return redirect(reverse('library:project_detail', args=[slug]) + '#forum')
|
||||
|
||||
|
||||
# ---------- API (autocomplete) ----------
|
||||
|
||||
def api_tags_autocomplete(request):
|
||||
|
||||
Reference in New Issue
Block a user