Files
thrilltrack-explorer/django/apps/reviews/admin.py
pacnpal 00985eac8d Implement reviews and voting system
- Added Review model with fields for user, content type, title, content, rating, visit metadata, helpful votes, moderation status, and timestamps.
- Created ReviewHelpfulVote model to track user votes on reviews.
- Implemented moderation workflow for reviews with approve and reject methods.
- Developed admin interface for managing reviews and helpful votes, including custom display methods and actions for bulk approval/rejection.
- Added migrations for the new models and their relationships.
- Ensured unique constraints and indexes for efficient querying.
2025-11-08 15:50:43 -05:00

216 lines
6.5 KiB
Python

from django.contrib import admin
from django.utils.html import format_html
from unfold.admin import ModelAdmin
from unfold.decorators import display
from .models import Review, ReviewHelpfulVote
@admin.register(Review)
class ReviewAdmin(ModelAdmin):
list_display = [
'id',
'user_link',
'entity_type',
'entity_link',
'rating_display',
'title',
'moderation_status_badge',
'helpful_score',
'created',
]
list_filter = [
'moderation_status',
'rating',
'created',
'content_type',
]
search_fields = [
'title',
'content',
'user__username',
'user__email',
]
readonly_fields = [
'user',
'content_type',
'object_id',
'content_object',
'helpful_votes',
'total_votes',
'helpful_percentage',
'created',
'modified',
]
fieldsets = (
('Review Information', {
'fields': (
'user',
'content_type',
'object_id',
'content_object',
'title',
'content',
'rating',
)
}),
('Visit Details', {
'fields': (
'visit_date',
'wait_time_minutes',
)
}),
('Voting Statistics', {
'fields': (
'helpful_votes',
'total_votes',
'helpful_percentage',
)
}),
('Moderation', {
'fields': (
'moderation_status',
'moderation_notes',
'moderated_by',
'moderated_at',
)
}),
('Timestamps', {
'fields': (
'created',
'modified',
)
}),
)
list_per_page = 50
@display(description='User', ordering='user__username')
def user_link(self, obj):
from django.urls import reverse
url = reverse('admin:users_user_change', args=[obj.user.pk])
return format_html('<a href="{}">{}</a>', url, obj.user.username)
@display(description='Entity Type', ordering='content_type')
def entity_type(self, obj):
return obj.content_type.model.title()
@display(description='Entity')
def entity_link(self, obj):
if obj.content_object:
from django.urls import reverse
model_name = obj.content_type.model
url = reverse(f'admin:entities_{model_name}_change', args=[obj.object_id])
return format_html('<a href="{}">{}</a>', url, str(obj.content_object))
return f"ID: {obj.object_id}"
@display(description='Rating', ordering='rating')
def rating_display(self, obj):
stars = '' * obj.rating
return format_html('<span title="{}/5">{}</span>', obj.rating, stars)
@display(description='Status', ordering='moderation_status')
def moderation_status_badge(self, obj):
colors = {
'pending': '#FFA500',
'approved': '#28A745',
'rejected': '#DC3545',
}
color = colors.get(obj.moderation_status, '#6C757D')
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 10px; '
'border-radius: 3px; font-weight: bold;">{}</span>',
color,
obj.get_moderation_status_display()
)
@display(description='Helpful Score')
def helpful_score(self, obj):
if obj.total_votes == 0:
return "No votes yet"
percentage = obj.helpful_percentage
return f"{obj.helpful_votes}/{obj.total_votes} ({percentage:.0f}%)"
def has_add_permission(self, request):
# Reviews should only be created by users via API
return False
def has_delete_permission(self, request, obj=None):
# Only superusers can delete reviews
return request.user.is_superuser
actions = ['approve_reviews', 'reject_reviews']
@admin.action(description='Approve selected reviews')
def approve_reviews(self, request, queryset):
count = 0
for review in queryset.filter(moderation_status='pending'):
review.approve(request.user, 'Bulk approved from admin')
count += 1
self.message_user(request, f'{count} reviews approved.')
@admin.action(description='Reject selected reviews')
def reject_reviews(self, request, queryset):
count = 0
for review in queryset.filter(moderation_status='pending'):
review.reject(request.user, 'Bulk rejected from admin')
count += 1
self.message_user(request, f'{count} reviews rejected.')
@admin.register(ReviewHelpfulVote)
class ReviewHelpfulVoteAdmin(ModelAdmin):
list_display = [
'id',
'review_link',
'user_link',
'vote_type',
'created',
]
list_filter = [
'is_helpful',
'created',
]
search_fields = [
'review__title',
'user__username',
'user__email',
]
readonly_fields = [
'review',
'user',
'is_helpful',
'created',
'modified',
]
list_per_page = 50
@display(description='Review', ordering='review__title')
def review_link(self, obj):
from django.urls import reverse
url = reverse('admin:reviews_review_change', args=[obj.review.pk])
return format_html('<a href="{}">{}</a>', url, obj.review.title)
@display(description='User', ordering='user__username')
def user_link(self, obj):
from django.urls import reverse
url = reverse('admin:users_user_change', args=[obj.user.pk])
return format_html('<a href="{}">{}</a>', url, obj.user.username)
@display(description='Vote', ordering='is_helpful')
def vote_type(self, obj):
if obj.is_helpful:
return format_html('<span style="color: green;">👍 Helpful</span>')
else:
return format_html('<span style="color: red;">👎 Not Helpful</span>')
def has_add_permission(self, request):
# Votes should only be created by users via API
return False
def has_change_permission(self, request, obj=None):
# Votes should not be changed after creation
return False
def has_delete_permission(self, request, obj=None):
# Only superusers can delete votes
return request.user.is_superuser