Refactor code structure and remove redundant changes

This commit is contained in:
pacnpal
2025-11-09 16:31:34 -05:00
parent 2884bc23ce
commit eb68cf40c6
1080 changed files with 27361 additions and 56687 deletions

View File

@@ -0,0 +1,330 @@
"""
Contact submission API endpoints.
Handles user contact form submissions and admin management.
"""
from typing import Optional
from uuid import UUID
from ninja import Router
from django.db.models import Q, Count, Avg
from django.utils import timezone
from datetime import timedelta
from apps.contact.models import ContactSubmission
from apps.contact.tasks import send_contact_confirmation_email, notify_admins_new_contact, send_contact_resolution_email
from apps.users.permissions import require_role
from api.v1.schemas import (
ContactSubmissionCreate,
ContactSubmissionUpdate,
ContactSubmissionOut,
ContactSubmissionListOut,
ContactSubmissionStatsOut,
MessageSchema,
ErrorSchema,
)
router = Router(tags=["Contact"])
# ============================================================================
# Public Endpoints
# ============================================================================
@router.post("/submit", response={200: ContactSubmissionOut, 400: ErrorSchema})
def submit_contact_form(request, data: ContactSubmissionCreate):
"""
Submit a contact form.
Available to both authenticated and anonymous users.
"""
try:
# Create the contact submission
contact = ContactSubmission.objects.create(
name=data.name,
email=data.email,
subject=data.subject,
message=data.message,
category=data.category,
user=request.auth if request.auth else None
)
# Send confirmation email to user
send_contact_confirmation_email.delay(str(contact.id))
# Notify admins
notify_admins_new_contact.delay(str(contact.id))
# Prepare response
response = ContactSubmissionOut.from_orm(contact)
response.user_email = contact.user.email if contact.user else None
return 200, response
except Exception as e:
return 400, {"error": "Failed to submit contact form", "detail": str(e)}
# ============================================================================
# Admin/Moderator Endpoints
# ============================================================================
@router.get("/", response={200: ContactSubmissionListOut, 403: ErrorSchema})
@require_role(['moderator', 'admin'])
def list_contact_submissions(
request,
page: int = 1,
page_size: int = 20,
status: Optional[str] = None,
category: Optional[str] = None,
assigned_to_me: bool = False,
search: Optional[str] = None
):
"""
List contact submissions (moderators/admins only).
Supports filtering and pagination.
"""
queryset = ContactSubmission.objects.all()
# Apply filters
if status:
queryset = queryset.filter(status=status)
if category:
queryset = queryset.filter(category=category)
if assigned_to_me and request.auth:
queryset = queryset.filter(assigned_to=request.auth)
if search:
queryset = queryset.filter(
Q(ticket_number__icontains=search) |
Q(name__icontains=search) |
Q(email__icontains=search) |
Q(subject__icontains=search) |
Q(message__icontains=search)
)
# Get total count
total = queryset.count()
# Apply pagination
start = (page - 1) * page_size
end = start + page_size
contacts = queryset[start:end]
# Prepare response
items = []
for contact in contacts:
item = ContactSubmissionOut.from_orm(contact)
item.user_email = contact.user.email if contact.user else None
item.assigned_to_email = contact.assigned_to.email if contact.assigned_to else None
item.resolved_by_email = contact.resolved_by.email if contact.resolved_by else None
items.append(item)
return {
"items": items,
"total": total,
"page": page,
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size
}
@router.get("/{contact_id}", response={200: ContactSubmissionOut, 404: ErrorSchema, 403: ErrorSchema})
@require_role(['moderator', 'admin'])
def get_contact_submission(request, contact_id: UUID):
"""
Get a specific contact submission by ID (moderators/admins only).
"""
try:
contact = ContactSubmission.objects.get(id=contact_id)
response = ContactSubmissionOut.from_orm(contact)
response.user_email = contact.user.email if contact.user else None
response.assigned_to_email = contact.assigned_to.email if contact.assigned_to else None
response.resolved_by_email = contact.resolved_by.email if contact.resolved_by else None
return 200, response
except ContactSubmission.DoesNotExist:
return 404, {"error": "Contact submission not found"}
@router.patch("/{contact_id}", response={200: ContactSubmissionOut, 404: ErrorSchema, 400: ErrorSchema, 403: ErrorSchema})
@require_role(['moderator', 'admin'])
def update_contact_submission(request, contact_id: UUID, data: ContactSubmissionUpdate):
"""
Update a contact submission (moderators/admins only).
Used to change status, assign, or add notes.
"""
try:
contact = ContactSubmission.objects.get(id=contact_id)
# Track if status changed to resolved
status_changed_to_resolved = False
old_status = contact.status
# Update fields
if data.status is not None:
contact.status = data.status
if data.status == 'resolved' and old_status != 'resolved':
status_changed_to_resolved = True
contact.resolved_by = request.auth
contact.resolved_at = timezone.now()
if data.assigned_to_id is not None:
from apps.users.models import User
try:
contact.assigned_to = User.objects.get(id=data.assigned_to_id)
except User.DoesNotExist:
return 400, {"error": "Invalid user ID for assignment"}
if data.admin_notes is not None:
contact.admin_notes = data.admin_notes
contact.save()
# Send resolution email if status changed to resolved
if status_changed_to_resolved:
send_contact_resolution_email.delay(str(contact.id))
# Prepare response
response = ContactSubmissionOut.from_orm(contact)
response.user_email = contact.user.email if contact.user else None
response.assigned_to_email = contact.assigned_to.email if contact.assigned_to else None
response.resolved_by_email = contact.resolved_by.email if contact.resolved_by else None
return 200, response
except ContactSubmission.DoesNotExist:
return 404, {"error": "Contact submission not found"}
except Exception as e:
return 400, {"error": "Failed to update contact submission", "detail": str(e)}
@router.post("/{contact_id}/assign-to-me", response={200: MessageSchema, 404: ErrorSchema, 403: ErrorSchema})
@require_role(['moderator', 'admin'])
def assign_to_me(request, contact_id: UUID):
"""
Assign a contact submission to the current user (moderators/admins only).
"""
try:
contact = ContactSubmission.objects.get(id=contact_id)
contact.assigned_to = request.auth
contact.save()
return 200, {
"message": f"Contact submission {contact.ticket_number} assigned to you",
"success": True
}
except ContactSubmission.DoesNotExist:
return 404, {"error": "Contact submission not found"}
@router.post("/{contact_id}/mark-resolved", response={200: MessageSchema, 404: ErrorSchema, 403: ErrorSchema})
@require_role(['moderator', 'admin'])
def mark_resolved(request, contact_id: UUID):
"""
Mark a contact submission as resolved (moderators/admins only).
"""
try:
contact = ContactSubmission.objects.get(id=contact_id)
if contact.status == 'resolved':
return 200, {
"message": f"Contact submission {contact.ticket_number} is already resolved",
"success": True
}
contact.status = 'resolved'
contact.resolved_by = request.auth
contact.resolved_at = timezone.now()
contact.save()
# Send resolution email
send_contact_resolution_email.delay(str(contact.id))
return 200, {
"message": f"Contact submission {contact.ticket_number} marked as resolved",
"success": True
}
except ContactSubmission.DoesNotExist:
return 404, {"error": "Contact submission not found"}
@router.get("/stats/overview", response={200: ContactSubmissionStatsOut, 403: ErrorSchema})
@require_role(['moderator', 'admin'])
def get_contact_stats(request):
"""
Get contact submission statistics (moderators/admins only).
"""
# Get counts by status
total_submissions = ContactSubmission.objects.count()
pending_submissions = ContactSubmission.objects.filter(status='pending').count()
in_progress_submissions = ContactSubmission.objects.filter(status='in_progress').count()
resolved_submissions = ContactSubmission.objects.filter(status='resolved').count()
archived_submissions = ContactSubmission.objects.filter(status='archived').count()
# Get counts by category
submissions_by_category = dict(
ContactSubmission.objects.values('category').annotate(
count=Count('id')
).values_list('category', 'count')
)
# Calculate average resolution time
resolved_contacts = ContactSubmission.objects.filter(
status='resolved',
resolved_at__isnull=False
).exclude(created_at=None)
avg_resolution_time = None
if resolved_contacts.exists():
total_time = sum([
(contact.resolved_at - contact.created_at).total_seconds() / 3600
for contact in resolved_contacts
])
avg_resolution_time = total_time / resolved_contacts.count()
# Get recent submissions
recent = ContactSubmission.objects.order_by('-created_at')[:5]
recent_submissions = []
for contact in recent:
item = ContactSubmissionOut.from_orm(contact)
item.user_email = contact.user.email if contact.user else None
item.assigned_to_email = contact.assigned_to.email if contact.assigned_to else None
item.resolved_by_email = contact.resolved_by.email if contact.resolved_by else None
recent_submissions.append(item)
return {
"total_submissions": total_submissions,
"pending_submissions": pending_submissions,
"in_progress_submissions": in_progress_submissions,
"resolved_submissions": resolved_submissions,
"archived_submissions": archived_submissions,
"submissions_by_category": submissions_by_category,
"average_resolution_time_hours": avg_resolution_time,
"recent_submissions": recent_submissions
}
@router.delete("/{contact_id}", response={200: MessageSchema, 404: ErrorSchema, 403: ErrorSchema})
@require_role(['admin'])
def delete_contact_submission(request, contact_id: UUID):
"""
Delete a contact submission (admins only).
Use with caution - typically should archive instead.
"""
try:
contact = ContactSubmission.objects.get(id=contact_id)
ticket_number = contact.ticket_number
contact.delete()
return 200, {
"message": f"Contact submission {ticket_number} deleted",
"success": True
}
except ContactSubmission.DoesNotExist:
return 404, {"error": "Contact submission not found"}