mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 06:51:13 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
202
django-backend/apps/reviews/models.py
Normal file
202
django-backend/apps/reviews/models.py
Normal file
@@ -0,0 +1,202 @@
|
||||
from django.db import models
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from model_utils.models import TimeStampedModel
|
||||
import pghistory
|
||||
|
||||
|
||||
@pghistory.track()
|
||||
class Review(TimeStampedModel):
|
||||
"""
|
||||
User reviews for parks or rides.
|
||||
|
||||
Users can leave reviews with ratings, text, photos, and metadata like visit date.
|
||||
Reviews support helpful voting and go through moderation workflow.
|
||||
"""
|
||||
|
||||
# User who created the review
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='reviews'
|
||||
)
|
||||
|
||||
# Generic relation - can review either a Park or a Ride
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to={'model__in': ('park', 'ride')}
|
||||
)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
# Review content
|
||||
title = models.CharField(max_length=200)
|
||||
content = models.TextField()
|
||||
rating = models.IntegerField(
|
||||
validators=[MinValueValidator(1), MaxValueValidator(5)],
|
||||
help_text="Rating from 1 to 5 stars"
|
||||
)
|
||||
|
||||
# Visit metadata
|
||||
visit_date = models.DateField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Date the user visited"
|
||||
)
|
||||
wait_time_minutes = models.PositiveIntegerField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Wait time in minutes"
|
||||
)
|
||||
|
||||
# Helpful voting system
|
||||
helpful_votes = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text="Number of users who found this review helpful"
|
||||
)
|
||||
total_votes = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text="Total number of votes (helpful + not helpful)"
|
||||
)
|
||||
|
||||
# Moderation status
|
||||
MODERATION_PENDING = 'pending'
|
||||
MODERATION_APPROVED = 'approved'
|
||||
MODERATION_REJECTED = 'rejected'
|
||||
|
||||
MODERATION_STATUS_CHOICES = [
|
||||
(MODERATION_PENDING, 'Pending'),
|
||||
(MODERATION_APPROVED, 'Approved'),
|
||||
(MODERATION_REJECTED, 'Rejected'),
|
||||
]
|
||||
|
||||
moderation_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=MODERATION_STATUS_CHOICES,
|
||||
default=MODERATION_PENDING,
|
||||
db_index=True
|
||||
)
|
||||
moderation_notes = models.TextField(
|
||||
blank=True,
|
||||
help_text="Notes from moderator"
|
||||
)
|
||||
moderated_at = models.DateTimeField(null=True, blank=True)
|
||||
moderated_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='moderated_reviews'
|
||||
)
|
||||
|
||||
# Link to ContentSubmission (Sacred Pipeline integration)
|
||||
submission = models.ForeignKey(
|
||||
'moderation.ContentSubmission',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='reviews',
|
||||
help_text="ContentSubmission that created this review"
|
||||
)
|
||||
|
||||
# Photos related to this review (via media.Photo model with generic relation)
|
||||
photos = GenericRelation('media.Photo')
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created']
|
||||
indexes = [
|
||||
models.Index(fields=['content_type', 'object_id']),
|
||||
models.Index(fields=['user', 'created']),
|
||||
models.Index(fields=['moderation_status', 'created']),
|
||||
models.Index(fields=['rating']),
|
||||
]
|
||||
# A user can only review a specific park/ride once
|
||||
unique_together = [['user', 'content_type', 'object_id']]
|
||||
|
||||
def __str__(self):
|
||||
entity_type = self.content_type.model
|
||||
return f"{self.user.username}'s review of {entity_type} #{self.object_id}"
|
||||
|
||||
@property
|
||||
def helpful_percentage(self):
|
||||
"""Calculate percentage of helpful votes."""
|
||||
if self.total_votes == 0:
|
||||
return None
|
||||
return (self.helpful_votes / self.total_votes) * 100
|
||||
|
||||
@property
|
||||
def is_approved(self):
|
||||
"""Check if review is approved."""
|
||||
return self.moderation_status == self.MODERATION_APPROVED
|
||||
|
||||
@property
|
||||
def is_pending(self):
|
||||
"""Check if review is pending moderation."""
|
||||
return self.moderation_status == self.MODERATION_PENDING
|
||||
|
||||
|
||||
class ReviewHelpfulVote(TimeStampedModel):
|
||||
"""
|
||||
Track individual helpful votes to prevent duplicate voting.
|
||||
"""
|
||||
review = models.ForeignKey(
|
||||
Review,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='vote_records'
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='review_votes'
|
||||
)
|
||||
is_helpful = models.BooleanField(
|
||||
help_text="True if user found review helpful, False if not helpful"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = [['review', 'user']]
|
||||
indexes = [
|
||||
models.Index(fields=['review', 'user']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
vote_type = "helpful" if self.is_helpful else "not helpful"
|
||||
return f"{self.user.username} voted {vote_type} on review #{self.review.id}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Update review vote counts when saving."""
|
||||
is_new = self.pk is None
|
||||
old_is_helpful = None
|
||||
|
||||
if not is_new:
|
||||
# Get old value before update
|
||||
old_vote = ReviewHelpfulVote.objects.get(pk=self.pk)
|
||||
old_is_helpful = old_vote.is_helpful
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Update review vote counts
|
||||
if is_new:
|
||||
# New vote
|
||||
self.review.total_votes += 1
|
||||
if self.is_helpful:
|
||||
self.review.helpful_votes += 1
|
||||
self.review.save()
|
||||
elif old_is_helpful != self.is_helpful:
|
||||
# Vote changed
|
||||
if self.is_helpful:
|
||||
self.review.helpful_votes += 1
|
||||
else:
|
||||
self.review.helpful_votes -= 1
|
||||
self.review.save()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Update review vote counts when deleting."""
|
||||
self.review.total_votes -= 1
|
||||
if self.is_helpful:
|
||||
self.review.helpful_votes -= 1
|
||||
self.review.save()
|
||||
super().delete(*args, **kwargs)
|
||||
Reference in New Issue
Block a user