mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 10:31:13 -05:00
- 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.
216 lines
6.5 KiB
Python
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
|