742 lines
27 KiB
Python
742 lines
27 KiB
Python
import json
|
||
import logging
|
||
import random
|
||
import time
|
||
import requests
|
||
import threading
|
||
from django.shortcuts import render, redirect, get_object_or_404
|
||
from django.http import JsonResponse, HttpResponse
|
||
from django.views.decorators.http import require_http_methods
|
||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||
from django.core.cache import cache
|
||
from django.contrib.auth.models import User
|
||
from django.conf import settings
|
||
from django.db.models import Q, Avg
|
||
from django.utils import timezone
|
||
import uuid
|
||
|
||
from .models import Competition, CompetitionEnrollment, Project, Score, ScoreDimension, Comment, ProjectFile
|
||
from shop.models import WeChatUser
|
||
from shop.sms_utils import send_sms
|
||
from ai_services.models import TranscriptionTask, AIEvaluation
|
||
from ai_services.services import AliyunTingwuService
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# --- Helper Functions ---
|
||
|
||
def get_client_ip(request):
|
||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||
if x_forwarded_for:
|
||
ip = x_forwarded_for.split(',')[0]
|
||
else:
|
||
ip = request.META.get('REMOTE_ADDR')
|
||
return ip
|
||
|
||
def log_audit(request, action, target, result="SUCCESS", details=""):
|
||
judge_id = request.session.get('judge_id')
|
||
phone = request.session.get('judge_phone', 'Unknown')
|
||
role = request.session.get('judge_role', 'unknown')
|
||
ip = get_client_ip(request)
|
||
timestamp = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
log_entry = f"[{timestamp}] IP:{ip} | Phone:{phone} | Role:{role} | Action:{action} | Target:{target} | Result:{result} | Details:{details}\n"
|
||
|
||
# Write to a file
|
||
try:
|
||
with open(settings.BASE_DIR / 'judge_audit.log', 'a', encoding='utf-8') as f:
|
||
f.write(log_entry)
|
||
except Exception as e:
|
||
logger.error(f"Failed to write audit log: {e}")
|
||
|
||
def judge_required(view_func):
|
||
def wrapper(request, *args, **kwargs):
|
||
if not request.session.get('judge_id'):
|
||
return redirect('judge_login')
|
||
return view_func(request, *args, **kwargs)
|
||
return wrapper
|
||
|
||
def check_contestant_access(view_func):
|
||
"""
|
||
Check if the user is allowed to access.
|
||
Contestants have limited access.
|
||
"""
|
||
def wrapper(request, *args, **kwargs):
|
||
if not request.session.get('judge_id'):
|
||
return redirect('judge_login')
|
||
|
||
role = request.session.get('judge_role')
|
||
if role == 'contestant':
|
||
# Some views might be restricted for contestants
|
||
# For now, this decorator just ensures login, but specific views handle logic
|
||
pass
|
||
|
||
return view_func(request, *args, **kwargs)
|
||
return wrapper
|
||
|
||
# --- Views ---
|
||
|
||
def admin_entry(request):
|
||
"""Entry point for /competition/admin"""
|
||
if request.session.get('judge_id'):
|
||
return redirect('judge_dashboard')
|
||
return redirect('judge_login')
|
||
|
||
@csrf_exempt
|
||
def login_view(request):
|
||
if request.method == 'GET':
|
||
return render(request, 'judge/login.html')
|
||
|
||
phone = request.POST.get('phone')
|
||
code = request.POST.get('code')
|
||
|
||
if not phone or not code:
|
||
return render(request, 'judge/login.html', {'error': '请输入手机号和验证码'})
|
||
|
||
# Verify Code
|
||
cached_code = cache.get(f"sms_code_{phone}")
|
||
# Universal pass code for development/testing
|
||
if code != cached_code and code != '888888':
|
||
return render(request, 'judge/login.html', {'error': '验证码错误 or expired'})
|
||
|
||
# Check User
|
||
try:
|
||
user = WeChatUser.objects.filter(phone_number=phone).first()
|
||
if not user:
|
||
return render(request, 'judge/login.html', {'error': '该手机号未绑定用户'})
|
||
|
||
# Check roles
|
||
# Priority: Judge > Guest > Contestant (if allowed)
|
||
is_judge = CompetitionEnrollment.objects.filter(user=user, role='judge').exists()
|
||
is_guest = CompetitionEnrollment.objects.filter(user=user, role='guest').exists()
|
||
|
||
role = None
|
||
if is_judge:
|
||
role = 'judge'
|
||
elif is_guest:
|
||
role = 'guest'
|
||
else:
|
||
# Check if contestant in any competition with allow_contestant_grading=True
|
||
contestant_enrollments = CompetitionEnrollment.objects.filter(
|
||
user=user,
|
||
role='contestant',
|
||
competition__allow_contestant_grading=True
|
||
)
|
||
if contestant_enrollments.exists():
|
||
role = 'contestant'
|
||
|
||
if not role:
|
||
return render(request, 'judge/login.html', {'error': '您没有权限登录系统'})
|
||
|
||
# Login Success
|
||
request.session['judge_id'] = user.id
|
||
request.session['judge_phone'] = phone
|
||
request.session['judge_name'] = user.nickname
|
||
request.session['judge_role'] = role
|
||
|
||
log_audit(request, 'LOGIN', 'System', 'SUCCESS', f"User {user.nickname} logged in as {role}")
|
||
|
||
return redirect('judge_dashboard')
|
||
|
||
except Exception as e:
|
||
logger.error(f"Login error: {e}")
|
||
return render(request, 'judge/login.html', {'error': '系统错误'})
|
||
|
||
@csrf_exempt
|
||
def send_code(request):
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': 'Method not allowed'})
|
||
|
||
try:
|
||
data = json.loads(request.body)
|
||
phone = data.get('phone')
|
||
|
||
if not phone or len(phone) != 11:
|
||
return JsonResponse({'success': False, 'message': 'Invalid phone number'})
|
||
|
||
# Generate Code
|
||
code = str(random.randint(100000, 999999)) # 6 digits to match typical SMS
|
||
cache.set(f"sms_code_{phone}", code, timeout=300) # 5 mins
|
||
|
||
# Send SMS using the specified API
|
||
def _send_async():
|
||
try:
|
||
api_url = "https://data.tangledup-ai.com/api/send-sms"
|
||
payload = {
|
||
"phone_number": phone,
|
||
"code": code,
|
||
"template_code": "SMS_493295002",
|
||
"sign_name": "叠加态科技云南",
|
||
"additionalProp1": {}
|
||
}
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"accept": "application/json"
|
||
}
|
||
response = requests.post(api_url, json=payload, headers=headers, timeout=15)
|
||
logger.info(f"SMS Response for {phone}: {response.status_code} - {response.text}")
|
||
except Exception as e:
|
||
logger.error(f"发送短信异常: {str(e)}")
|
||
|
||
threading.Thread(target=_send_async).start()
|
||
|
||
return JsonResponse({'success': True})
|
||
except Exception as e:
|
||
return JsonResponse({'success': False, 'message': str(e)})
|
||
|
||
def logout_view(request):
|
||
log_audit(request, 'LOGOUT', 'System')
|
||
request.session.flush()
|
||
return redirect('judge_login')
|
||
|
||
@judge_required
|
||
def dashboard(request):
|
||
judge_id = request.session['judge_id']
|
||
role = request.session.get('judge_role', 'judge')
|
||
user = WeChatUser.objects.get(id=judge_id)
|
||
|
||
# Get competitions
|
||
if role == 'judge':
|
||
enrollments = CompetitionEnrollment.objects.filter(user=user, role='judge')
|
||
elif role == 'guest':
|
||
enrollments = CompetitionEnrollment.objects.filter(user=user, role='guest')
|
||
else:
|
||
# Contestant: only competitions allowing grading
|
||
enrollments = CompetitionEnrollment.objects.filter(
|
||
user=user,
|
||
role='contestant',
|
||
competition__allow_contestant_grading=True
|
||
)
|
||
|
||
competition_ids = enrollments.values_list('competition_id', flat=True)
|
||
|
||
# Get Projects
|
||
projects = Project.objects.filter(
|
||
competition_id__in=competition_ids,
|
||
status='submitted'
|
||
).select_related('contestant__user')
|
||
|
||
# Format for template
|
||
project_list = []
|
||
for p in projects:
|
||
# Check current score/grading status for this user
|
||
# Note: Score model links to 'judge' which is a CompetitionEnrollment
|
||
# We need the enrollment for this user in this competition
|
||
user_enrollment = enrollments.filter(competition=p.competition).first()
|
||
|
||
project_list.append({
|
||
'id': p.id,
|
||
'title': p.title,
|
||
'cover_image_url': p.cover_image_url or (p.cover_image.url if p.cover_image else ''),
|
||
'contestant_name': p.contestant.user.nickname,
|
||
'current_score': p.final_score, # Global score
|
||
'status_class': 'status-submitted',
|
||
'get_status_display': p.get_status_display()
|
||
})
|
||
|
||
return render(request, 'judge/dashboard.html', {
|
||
'projects': project_list,
|
||
'user_role': role,
|
||
'user_name': request.session.get('judge_name', '用户')
|
||
})
|
||
|
||
@judge_required
|
||
def project_detail_api(request, project_id):
|
||
judge_id = request.session['judge_id']
|
||
role = request.session.get('judge_role', 'judge')
|
||
user = WeChatUser.objects.get(id=judge_id)
|
||
project = get_object_or_404(Project, id=project_id)
|
||
|
||
# Check permission
|
||
# User must be enrolled in the project's competition with correct role/settings
|
||
if role == 'judge':
|
||
enrollment = CompetitionEnrollment.objects.filter(user=user, role='judge', competition=project.competition).first()
|
||
elif role == 'guest':
|
||
enrollment = CompetitionEnrollment.objects.filter(user=user, role='guest', competition=project.competition).first()
|
||
else:
|
||
enrollment = CompetitionEnrollment.objects.filter(
|
||
user=user,
|
||
role='contestant',
|
||
competition=project.competition,
|
||
competition__allow_contestant_grading=True
|
||
).first()
|
||
|
||
if not enrollment:
|
||
return JsonResponse({'error': 'No permission'}, status=403)
|
||
|
||
# Get Dimensions - 根据角色过滤
|
||
if role == 'contestant':
|
||
dimensions = ScoreDimension.objects.filter(
|
||
competition=project.competition,
|
||
is_public=True,
|
||
is_peer_review=True
|
||
).order_by('order')
|
||
else:
|
||
dimensions = ScoreDimension.objects.filter(
|
||
competition=project.competition,
|
||
is_public=True,
|
||
is_peer_review=False
|
||
).order_by('order')
|
||
|
||
# Get existing scores by THIS user
|
||
scores = Score.objects.filter(project=project, judge=enrollment)
|
||
score_map = {s.dimension_id: s.score for s in scores}
|
||
|
||
dim_data = []
|
||
for d in dimensions:
|
||
dim_data.append({
|
||
'id': d.id,
|
||
'name': d.name,
|
||
'weight': float(d.weight),
|
||
'max_score': d.max_score,
|
||
'current_score': float(score_map.get(d.id, 0))
|
||
})
|
||
|
||
# Get Comments
|
||
# If role is contestant, they CANNOT see other people's comments
|
||
history = []
|
||
current_comment = ""
|
||
|
||
if role in ['judge', 'guest']:
|
||
comments = Comment.objects.filter(project=project).order_by('-created_at')
|
||
for c in comments:
|
||
history.append({
|
||
'judge_name': c.judge.user.nickname,
|
||
'content': c.content,
|
||
'created_at': c.created_at.strftime("%Y-%m-%d %H:%M")
|
||
})
|
||
if c.judge.id == enrollment.id:
|
||
current_comment = c.content
|
||
else:
|
||
# Contestant: only see their own comment
|
||
my_comment = Comment.objects.filter(project=project, judge=enrollment).first()
|
||
if my_comment:
|
||
current_comment = my_comment.content
|
||
history.append({
|
||
'judge_name': user.nickname, # Self
|
||
'content': my_comment.content,
|
||
'created_at': my_comment.created_at.strftime("%Y-%m-%d %H:%M")
|
||
})
|
||
|
||
# Include AI results
|
||
latest_task = TranscriptionTask.objects.filter(project=project, status='SUCCEEDED').order_by('-created_at').first()
|
||
ai_data = None
|
||
if latest_task:
|
||
ai_data = {
|
||
'transcription': latest_task.transcription,
|
||
'summary': latest_task.summary,
|
||
'auto_chapters_data': latest_task.auto_chapters_data,
|
||
'transcription_data': latest_task.transcription_data
|
||
}
|
||
|
||
latest_task_any = TranscriptionTask.objects.filter(project=project).order_by('-created_at').first()
|
||
audio_url = latest_task_any.file_url if latest_task_any else None
|
||
|
||
# 计算各类评分(仅对评委和嘉宾可见)
|
||
judge_score_avg = None
|
||
peer_score_avg = None
|
||
ai_score_avg = None
|
||
final_score = float(project.final_score) if project.final_score else 0
|
||
|
||
if role in ['judge', 'guest']:
|
||
# 评委评分:所有评委的平均分(不包括选手互评维度)
|
||
judge_dimensions = ScoreDimension.objects.filter(
|
||
competition=project.competition,
|
||
is_public=True,
|
||
is_peer_review=False
|
||
)
|
||
judge_enrollments = CompetitionEnrollment.objects.filter(
|
||
competition=project.competition,
|
||
role='judge'
|
||
)
|
||
|
||
if judge_dimensions.exists() and judge_enrollments.exists():
|
||
judge_total = 0
|
||
judge_count = 0
|
||
for judge_enrollment in judge_enrollments:
|
||
judge_project_scores = Score.objects.filter(
|
||
project=project,
|
||
judge=judge_enrollment,
|
||
dimension__in=judge_dimensions
|
||
)
|
||
if judge_project_scores.exists():
|
||
judge_score = sum(
|
||
float(s.score) * float(s.dimension.weight)
|
||
for s in judge_project_scores
|
||
)
|
||
judge_total += judge_score
|
||
judge_count += 1
|
||
if judge_count > 0:
|
||
judge_score_avg = round(judge_total / judge_count, 2)
|
||
|
||
# 选手互评分:所有选手的平均分(仅互评维度)
|
||
peer_dimensions = ScoreDimension.objects.filter(
|
||
competition=project.competition,
|
||
is_peer_review=True
|
||
)
|
||
peer_enrollments = CompetitionEnrollment.objects.filter(
|
||
competition=project.competition,
|
||
role='contestant'
|
||
)
|
||
|
||
if peer_dimensions.exists() and peer_enrollments.exists():
|
||
peer_total = 0
|
||
peer_count = 0
|
||
for peer_enrollment in peer_enrollments:
|
||
peer_project_scores = Score.objects.filter(
|
||
project=project,
|
||
judge=peer_enrollment,
|
||
dimension__in=peer_dimensions
|
||
)
|
||
if peer_project_scores.exists():
|
||
peer_score = sum(
|
||
float(s.score) * float(s.dimension.weight)
|
||
for s in peer_project_scores
|
||
)
|
||
peer_total += peer_score
|
||
peer_count += 1
|
||
if peer_count > 0:
|
||
peer_score_avg = round(peer_total / peer_count, 2)
|
||
|
||
# AI评分:来自AIEvaluation的平均分
|
||
ai_evaluations = AIEvaluation.objects.filter(
|
||
task__project=project,
|
||
task__status='COMPLETED'
|
||
)
|
||
ai_scores = [e.score for e in ai_evaluations if e.score is not None]
|
||
if ai_scores:
|
||
ai_score_avg = round(sum(ai_scores) / len(ai_scores), 2)
|
||
|
||
data = {
|
||
'id': project.id,
|
||
'title': project.title,
|
||
'description': project.description,
|
||
'contestant_name': project.contestant.user.nickname,
|
||
'dimensions': dim_data,
|
||
'history_comments': history,
|
||
'current_comment': current_comment,
|
||
'ai_result': ai_data,
|
||
'audio_url': audio_url,
|
||
'can_grade': role == 'judge' or (role == 'contestant' and project.contestant.user != user),
|
||
# 评分细项(仅评委和嘉宾可见)
|
||
'score_details': {
|
||
'judge_score': judge_score_avg,
|
||
'peer_score': peer_score_avg,
|
||
'ai_score': ai_score_avg,
|
||
'final_score': final_score
|
||
} if role in ['judge', 'guest'] else None
|
||
}
|
||
|
||
# Specifically for guest: can_grade is False
|
||
if role == 'guest':
|
||
data['can_grade'] = False
|
||
|
||
return JsonResponse(data)
|
||
|
||
@judge_required
|
||
@csrf_exempt
|
||
def submit_score(request):
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': 'Method not allowed'})
|
||
|
||
try:
|
||
data = json.loads(request.body)
|
||
project_id = data.get('project_id')
|
||
comment_content = data.get('comment')
|
||
|
||
judge_id = request.session['judge_id']
|
||
role = request.session.get('judge_role', 'judge')
|
||
|
||
if role == 'guest':
|
||
return JsonResponse({'success': False, 'message': '嘉宾无权评分'})
|
||
|
||
user = WeChatUser.objects.get(id=judge_id)
|
||
project = get_object_or_404(Project, id=project_id)
|
||
|
||
enrollment = None
|
||
if role == 'judge':
|
||
enrollment = CompetitionEnrollment.objects.filter(user=user, role='judge', competition=project.competition).first()
|
||
else:
|
||
enrollment = CompetitionEnrollment.objects.filter(
|
||
user=user,
|
||
role='contestant',
|
||
competition=project.competition,
|
||
competition__allow_contestant_grading=True
|
||
).first()
|
||
|
||
if not enrollment:
|
||
return JsonResponse({'success': False, 'message': 'No permission'})
|
||
|
||
# Save Scores - 根据角色过滤维度
|
||
if role == 'contestant':
|
||
dimensions = ScoreDimension.objects.filter(
|
||
competition=project.competition,
|
||
is_public=True,
|
||
is_peer_review=True
|
||
)
|
||
else:
|
||
dimensions = ScoreDimension.objects.filter(
|
||
competition=project.competition,
|
||
is_public=True,
|
||
is_peer_review=False
|
||
)
|
||
for d in dimensions:
|
||
score_key = f'score_{d.id}'
|
||
if score_key in data:
|
||
val = data[score_key]
|
||
Score.objects.update_or_create(
|
||
project=project,
|
||
judge=enrollment,
|
||
dimension=d,
|
||
defaults={'score': val}
|
||
)
|
||
|
||
# Save Comment
|
||
if comment_content:
|
||
Comment.objects.update_or_create(
|
||
project=project,
|
||
judge=enrollment,
|
||
defaults={'content': comment_content}
|
||
)
|
||
|
||
# Recalculate Project Score
|
||
project.calculate_score()
|
||
|
||
log_audit(request, 'SCORE_UPDATE', f"Project {project.id}", 'SUCCESS')
|
||
|
||
return JsonResponse({'success': True})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Submit score error: {e}")
|
||
return JsonResponse({'success': False, 'message': str(e)})
|
||
|
||
@judge_required
|
||
@csrf_exempt
|
||
def upload_audio(request):
|
||
# Contestants cannot upload, but Guests can
|
||
role = request.session.get('judge_role')
|
||
if role not in ['judge', 'guest']:
|
||
return JsonResponse({'success': False, 'message': 'Permission denied'})
|
||
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': 'Method not allowed'})
|
||
|
||
judge_id = request.session['judge_id']
|
||
file_obj = request.FILES.get('file')
|
||
project_id = request.POST.get('project_id')
|
||
|
||
if not file_obj or not project_id:
|
||
return JsonResponse({'success': False, 'message': 'Missing file or project_id'})
|
||
|
||
try:
|
||
# Check permission
|
||
user = WeChatUser.objects.get(id=judge_id)
|
||
project = Project.objects.get(id=project_id)
|
||
|
||
# Verify judge/guest has access to this project's competition
|
||
enrollment = CompetitionEnrollment.objects.filter(
|
||
user=user,
|
||
role=role,
|
||
competition=project.competition
|
||
).first()
|
||
|
||
if not enrollment:
|
||
return JsonResponse({'success': False, 'message': 'No permission for this project'})
|
||
|
||
# Upload to OSS & Create Task
|
||
service = AliyunTingwuService()
|
||
if not service.bucket:
|
||
return JsonResponse({'success': False, 'message': 'OSS not configured'})
|
||
|
||
file_extension = file_obj.name.split('.')[-1]
|
||
file_name = f"transcription/{uuid.uuid4()}.{file_extension}"
|
||
oss_url = service.upload_to_oss(file_obj, file_name)
|
||
|
||
# Create Task Record
|
||
task = TranscriptionTask.objects.create(
|
||
project=project,
|
||
file_url=oss_url,
|
||
status=TranscriptionTask.Status.PENDING
|
||
)
|
||
|
||
# Call Tingwu
|
||
try:
|
||
tingwu_response = service.create_transcription_task(oss_url)
|
||
# Handle response format
|
||
if 'Data' in tingwu_response and isinstance(tingwu_response['Data'], dict):
|
||
task_id = tingwu_response['Data'].get('TaskId')
|
||
else:
|
||
task_id = tingwu_response.get('TaskId')
|
||
|
||
if task_id:
|
||
task.task_id = task_id
|
||
task.status = TranscriptionTask.Status.PROCESSING
|
||
task.save()
|
||
|
||
log_audit(request, 'UPLOAD_AUDIO', f"Task {task.id}", 'SUCCESS')
|
||
return JsonResponse({'success': True, 'task_id': task.id, 'file_url': oss_url})
|
||
else:
|
||
task.status = TranscriptionTask.Status.FAILED
|
||
task.error_message = "No TaskId returned"
|
||
task.save()
|
||
return JsonResponse({'success': False, 'message': 'Failed to create Tingwu task'})
|
||
|
||
except Exception as e:
|
||
task.status = TranscriptionTask.Status.FAILED
|
||
task.error_message = str(e)
|
||
task.save()
|
||
return JsonResponse({'success': False, 'message': f'Tingwu Error: {e}'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Upload error: {e}")
|
||
return JsonResponse({'success': False, 'message': str(e)})
|
||
|
||
@judge_required
|
||
@csrf_exempt
|
||
def upload_audio_url(request):
|
||
"""
|
||
处理 URL 上传音频的 API
|
||
通过给定的音频 URL 直接进行处理,无需上传文件
|
||
"""
|
||
role = request.session.get('judge_role')
|
||
if role not in ['judge', 'guest']:
|
||
return JsonResponse({'success': False, 'message': 'Permission denied'})
|
||
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': 'Method not allowed'})
|
||
|
||
import json
|
||
try:
|
||
data = json.loads(request.body)
|
||
except json.JSONDecodeError:
|
||
return JsonResponse({'success': False, 'message': 'Invalid JSON'})
|
||
|
||
audio_url = data.get('url')
|
||
project_id = data.get('project_id')
|
||
|
||
if not audio_url or not project_id:
|
||
return JsonResponse({'success': False, 'message': 'Missing url or project_id'})
|
||
|
||
# 验证 URL 格式
|
||
if not audio_url.startswith(('http://', 'https://')):
|
||
return JsonResponse({'success': False, 'message': 'Invalid URL format'})
|
||
|
||
judge_id = request.session['judge_id']
|
||
|
||
try:
|
||
# 验证权限
|
||
user = WeChatUser.objects.get(id=judge_id)
|
||
project = Project.objects.get(id=project_id)
|
||
|
||
enrollment = CompetitionEnrollment.objects.filter(
|
||
user=user,
|
||
role=role,
|
||
competition=project.competition
|
||
).first()
|
||
|
||
if not enrollment:
|
||
return JsonResponse({'success': False, 'message': 'No permission for this project'})
|
||
|
||
# 创建任务记录,使用 URL 作为 file_url
|
||
service = AliyunTingwuService()
|
||
|
||
task = TranscriptionTask.objects.create(
|
||
project=project,
|
||
file_url=audio_url,
|
||
status=TranscriptionTask.Status.PENDING
|
||
)
|
||
|
||
# 调用 Tingwu 服务
|
||
try:
|
||
tingwu_response = service.create_transcription_task(audio_url)
|
||
|
||
if 'Data' in tingwu_response and isinstance(tingwu_response['Data'], dict):
|
||
task_id = tingwu_response['Data'].get('TaskId')
|
||
else:
|
||
task_id = tingwu_response.get('TaskId')
|
||
|
||
if task_id:
|
||
task.task_id = task_id
|
||
task.status = TranscriptionTask.Status.PROCESSING
|
||
task.save()
|
||
|
||
log_audit(request, 'UPLOAD_AUDIO_URL', f"Task {task.id}", 'SUCCESS')
|
||
return JsonResponse({'success': True, 'task_id': task.id, 'file_url': audio_url})
|
||
else:
|
||
task.status = TranscriptionTask.Status.FAILED
|
||
task.error_message = "No TaskId returned"
|
||
task.save()
|
||
return JsonResponse({'success': False, 'message': 'Failed to create Tingwu task'})
|
||
|
||
except Exception as e:
|
||
task.status = TranscriptionTask.Status.FAILED
|
||
task.error_message = str(e)
|
||
task.save()
|
||
return JsonResponse({'success': False, 'message': f'Tingwu Error: {e}'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Upload URL error: {e}")
|
||
return JsonResponse({'success': False, 'message': str(e)})
|
||
|
||
@judge_required
|
||
def ai_manage(request):
|
||
# Contestants cannot access AI manage
|
||
role = request.session.get('judge_role')
|
||
if role not in ['judge', 'guest']:
|
||
return redirect('judge_dashboard')
|
||
|
||
judge_id = request.session['judge_id']
|
||
user = WeChatUser.objects.get(id=judge_id)
|
||
enrollments = CompetitionEnrollment.objects.filter(user=user, role=role)
|
||
competition_ids = enrollments.values_list('competition_id', flat=True)
|
||
|
||
# Get tasks for projects in these competitions
|
||
tasks = TranscriptionTask.objects.filter(
|
||
project__competition_id__in=competition_ids
|
||
).select_related('project').order_by('-created_at')
|
||
|
||
task_list = []
|
||
for t in tasks:
|
||
# Get Evaluation Score
|
||
# AIEvaluation is linked to Task
|
||
evals = t.ai_evaluations.all()
|
||
score = evals[0].score if evals else None
|
||
|
||
task_list.append({
|
||
'id': t.id,
|
||
'project': t.project,
|
||
'file_url': t.file_url,
|
||
'file_name': t.file_url.split('/')[-1] if t.file_url else 'Unknown',
|
||
'status': t.status,
|
||
'status_class': 'status-' + t.status.lower(), # CSS class
|
||
'get_status_display': t.get_status_display(),
|
||
'ai_score': score
|
||
})
|
||
|
||
return render(request, 'judge/ai_manage.html', {
|
||
'tasks': task_list,
|
||
'user_name': request.session.get('judge_name', '用户'),
|
||
'user_role': role
|
||
})
|
||
|
||
@judge_required
|
||
@csrf_exempt
|
||
def delete_ai_task(request, task_id):
|
||
role = request.session.get('judge_role')
|
||
if role not in ['judge', 'guest']:
|
||
return JsonResponse({'success': False, 'message': 'Permission denied'})
|
||
|
||
if request.method != 'POST':
|
||
return JsonResponse({'success': False, 'message': 'Method not allowed'})
|
||
|
||
try:
|
||
task = get_object_or_404(TranscriptionTask, id=task_id)
|
||
# Permission check
|
||
# ...
|
||
|
||
task.delete()
|
||
log_audit(request, 'DELETE_TASK', f"Task {task_id}", 'SUCCESS')
|
||
return JsonResponse({'success': True})
|
||
except Exception as e:
|
||
return JsonResponse({'success': False, 'message': str(e)})
|