mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 16:51:13 -05:00
Implement entity submission services for ThrillWiki
- Added BaseEntitySubmissionService as an abstract base for entity submissions. - Created specific submission services for entities: Park, Ride, Company, RideModel. - Implemented create, update, and delete functionalities with moderation workflow. - Enhanced logging and validation for required fields. - Addressed foreign key handling and special field processing for each entity type. - Noted existing issues with JSONField usage in Company submissions.
This commit is contained in:
@@ -28,7 +28,17 @@ from ..schemas import (
|
||||
VoteResponse,
|
||||
ErrorResponse,
|
||||
UserSchema,
|
||||
HistoryListResponse,
|
||||
HistoryEventDetailSchema,
|
||||
HistoryComparisonSchema,
|
||||
HistoryDiffCurrentSchema,
|
||||
FieldHistorySchema,
|
||||
HistoryActivitySummarySchema,
|
||||
RollbackRequestSchema,
|
||||
RollbackResponseSchema,
|
||||
ErrorSchema,
|
||||
)
|
||||
from ..services.history_service import HistoryService
|
||||
|
||||
router = Router(tags=["Reviews"])
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -583,3 +593,252 @@ def get_review_stats(request, entity_type: str, entity_id: UUID):
|
||||
'total_reviews': stats['total_reviews'] or 0,
|
||||
'rating_distribution': distribution,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# History Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.get(
|
||||
'/{review_id}/history/',
|
||||
response={200: HistoryListResponse, 404: ErrorSchema},
|
||||
summary="Get review history",
|
||||
description="Get historical changes for a review"
|
||||
)
|
||||
def get_review_history(
|
||||
request,
|
||||
review_id: int,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
date_from: Optional[str] = Query(None, description="Filter from date (YYYY-MM-DD)"),
|
||||
date_to: Optional[str] = Query(None, description="Filter to date (YYYY-MM-DD)")
|
||||
):
|
||||
"""Get history for a review."""
|
||||
from datetime import datetime
|
||||
|
||||
# Verify review exists
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
|
||||
# Parse dates if provided
|
||||
date_from_obj = datetime.fromisoformat(date_from).date() if date_from else None
|
||||
date_to_obj = datetime.fromisoformat(date_to).date() if date_to else None
|
||||
|
||||
# Get history
|
||||
offset = (page - 1) * page_size
|
||||
events, accessible_count = HistoryService.get_history(
|
||||
'review', str(review_id), request.user,
|
||||
date_from=date_from_obj, date_to=date_to_obj,
|
||||
limit=page_size, offset=offset
|
||||
)
|
||||
|
||||
# Format events
|
||||
formatted_events = []
|
||||
for event in events:
|
||||
formatted_events.append({
|
||||
'id': event['id'],
|
||||
'timestamp': event['timestamp'],
|
||||
'operation': event['operation'],
|
||||
'snapshot': event['snapshot'],
|
||||
'changed_fields': event.get('changed_fields'),
|
||||
'change_summary': event.get('change_summary', ''),
|
||||
'can_rollback': HistoryService.can_rollback(request.user)
|
||||
})
|
||||
|
||||
# Calculate pagination
|
||||
total_pages = (accessible_count + page_size - 1) // page_size
|
||||
|
||||
return {
|
||||
'entity_id': str(review_id),
|
||||
'entity_type': 'review',
|
||||
'entity_name': f"Review by {review.user.username}",
|
||||
'total_events': accessible_count,
|
||||
'accessible_events': accessible_count,
|
||||
'access_limited': HistoryService.is_access_limited(request.user),
|
||||
'access_reason': HistoryService.get_access_reason(request.user),
|
||||
'events': formatted_events,
|
||||
'pagination': {
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'total_pages': total_pages,
|
||||
'total_items': accessible_count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{review_id}/history/{event_id}/',
|
||||
response={200: HistoryEventDetailSchema, 404: ErrorSchema},
|
||||
summary="Get specific review history event",
|
||||
description="Get detailed information about a specific historical event"
|
||||
)
|
||||
def get_review_history_event(request, review_id: int, event_id: int):
|
||||
"""Get a specific history event for a review."""
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
event = HistoryService.get_event('review', event_id, request.user)
|
||||
|
||||
if not event:
|
||||
return 404, {"error": "Event not found or not accessible"}
|
||||
|
||||
return {
|
||||
'id': event['id'],
|
||||
'timestamp': event['timestamp'],
|
||||
'operation': event['operation'],
|
||||
'entity_id': str(review_id),
|
||||
'entity_type': 'review',
|
||||
'entity_name': f"Review by {review.user.username}",
|
||||
'snapshot': event['snapshot'],
|
||||
'changed_fields': event.get('changed_fields'),
|
||||
'metadata': event.get('metadata', {}),
|
||||
'can_rollback': HistoryService.can_rollback(request.user),
|
||||
'rollback_preview': None
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{review_id}/history/compare/',
|
||||
response={200: HistoryComparisonSchema, 400: ErrorSchema, 404: ErrorSchema},
|
||||
summary="Compare two review history events",
|
||||
description="Compare two historical events for a review"
|
||||
)
|
||||
def compare_review_history(
|
||||
request,
|
||||
review_id: int,
|
||||
event1: int = Query(..., description="First event ID"),
|
||||
event2: int = Query(..., description="Second event ID")
|
||||
):
|
||||
"""Compare two historical events for a review."""
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
|
||||
try:
|
||||
comparison = HistoryService.compare_events(
|
||||
'review', event1, event2, request.user
|
||||
)
|
||||
|
||||
if not comparison:
|
||||
return 404, {"error": "One or both events not found"}
|
||||
|
||||
return {
|
||||
'entity_id': str(review_id),
|
||||
'entity_type': 'review',
|
||||
'entity_name': f"Review by {review.user.username}",
|
||||
'event1': comparison['event1'],
|
||||
'event2': comparison['event2'],
|
||||
'differences': comparison['differences'],
|
||||
'changed_field_count': comparison['changed_field_count'],
|
||||
'unchanged_field_count': comparison['unchanged_field_count'],
|
||||
'time_between': comparison['time_between']
|
||||
}
|
||||
except ValueError as e:
|
||||
return 400, {"error": str(e)}
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{review_id}/history/{event_id}/diff-current/',
|
||||
response={200: HistoryDiffCurrentSchema, 404: ErrorSchema},
|
||||
summary="Compare historical event with current state",
|
||||
description="Compare a historical event with the current review state"
|
||||
)
|
||||
def diff_review_history_with_current(request, review_id: int, event_id: int):
|
||||
"""Compare historical event with current review state."""
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
|
||||
try:
|
||||
diff = HistoryService.compare_with_current(
|
||||
'review', event_id, review, request.user
|
||||
)
|
||||
|
||||
if not diff:
|
||||
return 404, {"error": "Event not found"}
|
||||
|
||||
return {
|
||||
'entity_id': str(review_id),
|
||||
'entity_type': 'review',
|
||||
'entity_name': f"Review by {review.user.username}",
|
||||
'event': diff['event'],
|
||||
'current_state': diff['current_state'],
|
||||
'differences': diff['differences'],
|
||||
'changed_field_count': diff['changed_field_count'],
|
||||
'time_since': diff['time_since']
|
||||
}
|
||||
except ValueError as e:
|
||||
return 404, {"error": str(e)}
|
||||
|
||||
|
||||
@router.post(
|
||||
'/{review_id}/history/{event_id}/rollback/',
|
||||
response={200: RollbackResponseSchema, 400: ErrorSchema, 403: ErrorSchema},
|
||||
summary="Rollback review to historical state",
|
||||
description="Rollback review to a historical state (Moderators/Admins only)"
|
||||
)
|
||||
def rollback_review(request, review_id: int, event_id: int, payload: RollbackRequestSchema):
|
||||
"""
|
||||
Rollback review to a historical state.
|
||||
|
||||
**Permission:** Moderators, Admins, Superusers only
|
||||
"""
|
||||
# Check authentication
|
||||
if not request.user or not request.user.is_authenticated:
|
||||
return 401, {"error": "Authentication required"}
|
||||
|
||||
# Check rollback permission
|
||||
if not HistoryService.can_rollback(request.user):
|
||||
return 403, {"error": "Only moderators and administrators can perform rollbacks"}
|
||||
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
|
||||
try:
|
||||
result = HistoryService.rollback_to_event(
|
||||
review, 'review', event_id, request.user,
|
||||
fields=payload.fields,
|
||||
comment=payload.comment,
|
||||
create_backup=payload.create_backup
|
||||
)
|
||||
return result
|
||||
except (ValueError, PermissionError) as e:
|
||||
return 400, {"error": str(e)}
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{review_id}/history/field/{field_name}/',
|
||||
response={200: FieldHistorySchema, 404: ErrorSchema},
|
||||
summary="Get field-specific history",
|
||||
description="Get history of changes to a specific review field"
|
||||
)
|
||||
def get_review_field_history(request, review_id: int, field_name: str):
|
||||
"""Get history of changes to a specific review field."""
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
|
||||
history = HistoryService.get_field_history(
|
||||
'review', str(review_id), field_name, request.user
|
||||
)
|
||||
|
||||
return {
|
||||
'entity_id': str(review_id),
|
||||
'entity_type': 'review',
|
||||
'entity_name': f"Review by {review.user.username}",
|
||||
'field': field_name,
|
||||
'field_type': 'CharField', # Could introspect this
|
||||
**history
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{review_id}/history/summary/',
|
||||
response={200: HistoryActivitySummarySchema, 404: ErrorSchema},
|
||||
summary="Get review activity summary",
|
||||
description="Get activity summary for a review"
|
||||
)
|
||||
def get_review_activity_summary(request, review_id: int):
|
||||
"""Get activity summary for a review."""
|
||||
review = get_object_or_404(Review, id=review_id)
|
||||
|
||||
summary = HistoryService.get_activity_summary(
|
||||
'review', str(review_id), request.user
|
||||
)
|
||||
|
||||
return {
|
||||
'entity_id': str(review_id),
|
||||
'entity_type': 'review',
|
||||
'entity_name': f"Review by {review.user.username}",
|
||||
**summary
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user