operations_project/apps/operation/views.py
2025-05-20 15:57:10 +08:00

786 lines
31 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.shortcuts import render
import json
import uuid
import logging
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.conf import settings
from django.utils import timezone
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
import os
from .models import OperatorAccount, PlatformAccount, Video
from .serializers import (
OperatorAccountSerializer, PlatformAccountSerializer, VideoSerializer,
MultiPlatformAccountSerializer
)
from .pagination import CustomPagination
logger = logging.getLogger(__name__)
class OperatorAccountViewSet(viewsets.ModelViewSet):
"""运营账号管理视图集"""
queryset = OperatorAccount.objects.all()
serializer_class = OperatorAccountSerializer
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
"""获取运营账号列表"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# 使用自定义分页器的响应
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response({
"code": 200,
"message": "获取运营账号列表成功",
"data": serializer.data
})
def retrieve(self, request, *args, **kwargs):
"""获取运营账号详情"""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({
"code": 200,
"message": "获取运营账号详情成功",
"data": serializer.data
})
def update(self, request, *args, **kwargs):
"""更新运营账号信息"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
"code": 200,
"message": "更新运营账号信息成功",
"data": serializer.data
})
def partial_update(self, request, *args, **kwargs):
"""部分更新运营账号信息"""
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
"""创建运营账号"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response({
"code": 200,
"message": "运营账号创建成功",
"data": serializer.data
}, status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
"""删除运营账号"""
operator = self.get_object()
operator.is_active = False # 软删除
operator.save()
return Response({
"code": 200,
"message": "运营账号已停用",
"data": None
})
class PlatformAccountViewSet(viewsets.ModelViewSet):
"""平台账号管理视图集"""
queryset = PlatformAccount.objects.all()
serializer_class = PlatformAccountSerializer
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
"""获取平台账号列表"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# 处理数据结构
response_data = serializer.data
restructured_data = []
for account_data in response_data:
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": account_data.pop("platform_name"),
"account_url": account_data.pop("account_url"),
"account_id": account_data.pop("account_id"),
"account_name": account_data.pop("account_name")
}
# 添加platforms字段作为数组
account_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not account_data.get("name"):
account_data["name"] = platform_info["platform_name"]
restructured_data.append(account_data)
# 使用自定义分页器的响应,但替换数据
return self.get_paginated_response(restructured_data)
serializer = self.get_serializer(queryset, many=True)
# 处理数据结构
response_data = serializer.data
restructured_data = []
for account_data in response_data:
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": account_data.pop("platform_name"),
"account_url": account_data.pop("account_url"),
"account_id": account_data.pop("account_id"),
"account_name": account_data.pop("account_name")
}
# 添加platforms字段作为数组
account_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not account_data.get("name"):
account_data["name"] = platform_info["platform_name"]
restructured_data.append(account_data)
return Response({
"code": 200,
"message": "获取平台账号列表成功",
"data": restructured_data
})
def retrieve(self, request, *args, **kwargs):
"""获取平台账号详情"""
instance = self.get_object()
serializer = self.get_serializer(instance)
# 处理数据结构
account_data = serializer.data
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": account_data.pop("platform_name"),
"account_url": account_data.pop("account_url"),
"account_id": account_data.pop("account_id"),
"account_name": account_data.pop("account_name")
}
# 添加platforms字段
account_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not account_data.get("name"):
account_data["name"] = platform_info["platform_name"]
return Response({
"code": 200,
"message": "获取平台账号详情成功",
"data": account_data
})
def update(self, request, *args, **kwargs):
"""更新平台账号信息"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
# 单独处理platforms字段多平台信息需要特殊处理
data = request.data.copy()
platforms_data = None
if 'platforms' in data and isinstance(data['platforms'], list):
platforms_data = data.pop('platforms')
# 如果有platforms数据先将第一个平台的信息移至顶层保证基本平台信息能正常更新
if platforms_data and len(platforms_data) > 0:
first_platform = platforms_data[0]
if 'platform_name' in first_platform:
data['platform_name'] = first_platform['platform_name']
if 'account_url' in first_platform:
data['account_url'] = first_platform['account_url']
if 'account_id' in first_platform:
data['account_id'] = first_platform['account_id']
if 'account_name' in first_platform:
data['account_name'] = first_platform['account_name']
serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
# 处理多平台信息
# 这里我们需要实现新的逻辑来处理额外的平台
# 注意:这需要修改模型结构或添加关联模型来支持多平台
# 由于当前模型结构限制,我们暂时只能在此记录其他平台数据
# 用于未来扩展,目前会在日志中记录这些信息
if platforms_data and len(platforms_data) > 1:
logger = logging.getLogger(__name__)
logger.info(f"接收到多平台数据,但当前版本仅支持一个平台。额外平台数据: {platforms_data[1:]}")
# 这里应该添加创建关联平台记录的代码
# 例如:
# for platform_data in platforms_data[1:]:
# RelatedPlatform.objects.create(
# primary_account=instance,
# platform_name=platform_data.get('platform_name', ''),
# account_id=platform_data.get('account_id', ''),
# account_name=platform_data.get('account_name', ''),
# account_url=platform_data.get('account_url', '')
# )
# 处理数据结构
account_data = serializer.data
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": account_data.pop("platform_name"),
"account_url": account_data.pop("account_url"),
"account_id": account_data.pop("account_id"),
"account_name": account_data.pop("account_name")
}
# 添加platforms字段
# 如果有platforms_data使用原始请求中的数据否则使用当前的单平台数据
if platforms_data:
account_data["platforms"] = platforms_data
else:
account_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not account_data.get("name"):
account_data["name"] = platform_info["platform_name"]
return Response({
"code": 200,
"message": "更新平台账号信息成功",
"data": account_data
})
def partial_update(self, request, *args, **kwargs):
"""部分更新平台账号信息"""
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
"""创建平台账号"""
# 传统单平台账号创建流程
print(f"{request.data}")
with transaction.atomic():
# 处理operator字段可能是字符串类型的ID
data = request.data.copy()
if 'operator' in data and isinstance(data['operator'], str):
try:
# 尝试通过ID查找运营账号
operator_id = data['operator']
try:
# 先尝试通过整数ID查找
operator_id_int = int(operator_id)
operator = OperatorAccount.objects.get(id=operator_id_int)
except (ValueError, OperatorAccount.DoesNotExist):
# 如果无法转换为整数或找不到对应账号,尝试通过用户名或真实姓名查找
operator = OperatorAccount.objects.filter(
Q(username=operator_id) | Q(real_name=operator_id)
).first()
if not operator:
return Response({
"code": 404,
"message": f"未找到运营账号: {operator_id}请提供有效的ID、用户名或真实姓名",
"data": None
}, status=status.HTTP_404_NOT_FOUND)
# 更新请求数据中的operator字段为找到的operator的ID
data['operator'] = operator.id
except Exception as e:
return Response({
"code": 400,
"message": f"处理运营账号ID时出错: {str(e)}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 创建平台账号
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
# 创建平台账号
self.perform_create(serializer)
# 处理响应数据
account_data = serializer.data
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": account_data.pop("platform_name"),
"account_url": account_data.pop("account_url"),
"account_id": account_data.pop("account_id"),
"account_name": account_data.pop("account_name")
}
# 添加platforms字段
account_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not account_data.get("name"):
account_data["name"] = platform_info["platform_name"]
return Response({
"code": 200,
"message": "平台账号创建成功",
"data": account_data
}, status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
"""删除平台账号"""
platform_account = self.get_object()
self.perform_destroy(platform_account)
return Response({
"code": 200,
"message": "平台账号已删除",
"data": None
})
@action(detail=True, methods=['post'])
def update_followers(self, request, pk=None):
"""更新平台账号粉丝数"""
platform_account = self.get_object()
followers_count = request.data.get('followers_count')
if not followers_count:
return Response({
"code": 400,
"message": "粉丝数不能为空",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 更新粉丝数
platform_account.followers_count = followers_count
platform_account.save()
# 准备响应数据,与其他方法保持一致
platform_data = self.get_serializer(platform_account).data
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": platform_data.pop("platform_name"),
"account_url": platform_data.pop("account_url"),
"account_id": platform_data.pop("account_id"),
"account_name": platform_data.pop("account_name")
}
# 添加platforms字段
platform_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not platform_data.get("name"):
platform_data["name"] = platform_info["platform_name"]
return Response({
"code": 200,
"message": "粉丝数更新成功",
"data": platform_data
})
@action(detail=True, methods=['post'])
def update_profile(self, request, pk=None):
"""更新平台账号的头像、标签和最后发布时间"""
platform_account = self.get_object()
# 获取更新的资料数据
profile_data = {}
# 处理标签
if 'tags' in request.data:
# 处理tags支持字符串或数组格式
tags = request.data['tags']
if isinstance(tags, list):
profile_data['tags'] = ','.join(tags)
else:
profile_data['tags'] = tags
# 处理头像
if 'profile_image' in request.data:
profile_data['profile_image'] = request.data['profile_image']
# 处理最后发布时间
if 'last_posting' in request.data:
try:
# 尝试解析时间字符串
from dateutil import parser
last_posting = parser.parse(request.data['last_posting'])
profile_data['last_posting'] = last_posting
except Exception as e:
return Response({
"code": 400,
"message": f"最后发布时间格式错误: {str(e)}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 处理名称
if 'name' in request.data:
profile_data['name'] = request.data['name']
if not profile_data:
return Response({
"code": 400,
"message": "没有提供任何需更新的资料数据",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 更新平台账号资料
for field, value in profile_data.items():
setattr(platform_account, field, value)
platform_account.save()
# 准备响应数据,与其他方法保持一致
platform_data = self.get_serializer(platform_account).data
# 提取平台信息并放入platforms字段
platform_info = {
"platform_name": platform_data.pop("platform_name"),
"account_url": platform_data.pop("account_url"),
"account_id": platform_data.pop("account_id"),
"account_name": platform_data.pop("account_name")
}
# 添加platforms字段
platform_data["platforms"] = [platform_info]
# 保留用户传入的name字段如果没有则使用platform_name
if not platform_data.get("name"):
platform_data["name"] = platform_info["platform_name"]
return Response({
"code": 200,
"message": "平台账号资料更新成功",
"data": platform_data
})
class VideoViewSet(viewsets.ModelViewSet):
"""视频管理视图集"""
queryset = Video.objects.all()
serializer_class = VideoSerializer
pagination_class = CustomPagination
def list(self, request, *args, **kwargs):
"""获取视频列表"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
# 使用自定义分页器的响应
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response({
"code": 200,
"message": "获取视频列表成功",
"data": serializer.data
})
def retrieve(self, request, *args, **kwargs):
"""获取视频详情"""
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response({
"code": 200,
"message": "获取视频详情成功",
"data": serializer.data
})
def update(self, request, *args, **kwargs):
"""更新视频信息"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
"code": 200,
"message": "更新视频信息成功",
"data": serializer.data
})
def partial_update(self, request, *args, **kwargs):
"""部分更新视频信息"""
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
"""创建视频"""
with transaction.atomic():
# 处理platform_account字段可能是字符串类型的ID
data = request.data.copy()
if 'platform_account' in data and isinstance(data['platform_account'], str):
try:
# 尝试通过ID查找平台账号
platform_id = data['platform_account']
try:
# 先尝试通过整数ID查找
platform_id_int = int(platform_id)
platform = PlatformAccount.objects.get(id=platform_id_int)
except (ValueError, PlatformAccount.DoesNotExist):
# 如果无法转换为整数或找不到对应账号尝试通过账号名称或账号ID查找
platform = PlatformAccount.objects.filter(
Q(account_name=platform_id) | Q(account_id=platform_id)
).first()
if not platform:
return Response({
"code": 404,
"message": f"未找到平台账号: {platform_id}请提供有效的ID、账号名称或账号ID",
"data": None
}, status=status.HTTP_404_NOT_FOUND)
# 更新请求数据中的platform_account字段为找到的platform的ID
data['platform_account'] = platform.id
except Exception as e:
return Response({
"code": 400,
"message": f"处理平台账号ID时出错: {str(e)}",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 创建视频
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
# 创建视频
self.perform_create(serializer)
return Response({
"code": 200,
"message": "视频创建成功",
"data": serializer.data
}, status=status.HTTP_201_CREATED)
def destroy(self, request, *args, **kwargs):
"""删除视频记录"""
video = self.get_object()
self.perform_destroy(video)
return Response({
"code": 200,
"message": "视频记录已删除",
"data": None
})
@action(detail=True, methods=['post'])
def update_stats(self, request, pk=None):
"""更新视频统计数据"""
video = self.get_object()
# 获取更新的统计数据
stats = {}
for field in ['views_count', 'likes_count', 'comments_count', 'shares_count']:
if field in request.data:
stats[field] = request.data[field]
if not stats:
return Response({
"code": 400,
"message": "没有提供任何统计数据",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 更新视频统计数据
for field, value in stats.items():
setattr(video, field, value)
video.save()
return Response({
"code": 200,
"message": "视频统计数据更新成功",
"data": {
"id": video.id,
"title": video.title,
"views_count": video.views_count,
"likes_count": video.likes_count,
"comments_count": video.comments_count,
"shares_count": video.shares_count
}
})
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""发布视频并更新状态"""
video = self.get_object()
# 检查视频状态
if video.status not in ['draft', 'scheduled']:
return Response({
"code": 400,
"message": f"当前视频状态为 {video.get_status_display()},无法发布",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 获取视频URL
video_url = request.data.get('video_url')
if not video_url:
return Response({
"code": 400,
"message": "未提供视频URL",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 更新视频状态和URL
video.video_url = video_url
video.status = 'published'
video.publish_time = timezone.now()
video.save()
return Response({
"code": 200,
"message": "视频已成功发布",
"data": {
"id": video.id,
"title": video.title,
"status": video.status,
"video_url": video.video_url,
"publish_time": video.publish_time
}
})
@action(detail=False, methods=['post'])
def upload_video(self, request):
"""上传视频文件并创建视频记录"""
try:
# 获取上传的视频文件
video_file = request.FILES.get('video_file')
if not video_file:
return Response({
"code": 400,
"message": "未提供视频文件",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 获取平台账号ID
platform_account_id = request.data.get('platform_account')
if not platform_account_id:
return Response({
"code": 400,
"message": "未提供平台账号ID",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
try:
platform_account = PlatformAccount.objects.get(id=platform_account_id)
except PlatformAccount.DoesNotExist:
return Response({
"code": 404,
"message": f"未找到ID为{platform_account_id}的平台账号",
"data": None
}, status=status.HTTP_404_NOT_FOUND)
# 创建保存视频的目录
import os
from django.conf import settings
# 确保文件保存目录存在
media_root = getattr(settings, 'MEDIA_ROOT', os.path.join(settings.BASE_DIR, 'media'))
videos_dir = os.path.join(media_root, 'videos')
account_dir = os.path.join(videos_dir, f"{platform_account.platform_name}_{platform_account.account_name}")
if not os.path.exists(videos_dir):
os.makedirs(videos_dir)
if not os.path.exists(account_dir):
os.makedirs(account_dir)
# 生成唯一的文件名
import time
timestamp = int(time.time())
file_name = f"{timestamp}_{video_file.name}"
file_path = os.path.join(account_dir, file_name)
# 保存视频文件
with open(file_path, 'wb+') as destination:
for chunk in video_file.chunks():
destination.write(chunk)
# 创建视频记录
video_data = {
'platform_account': platform_account,
'title': request.data.get('title', os.path.splitext(video_file.name)[0]),
'description': request.data.get('description', ''),
'local_path': file_path,
'status': 'draft',
'tags': request.data.get('tags', '')
}
# 创建视频记录
video = Video.objects.create(**video_data)
return Response({
"code": 200,
"message": "视频上传成功",
"data": {
"id": video.id,
"title": video.title,
"status": video.get_status_display(),
}
}, status=status.HTTP_201_CREATED)
except Exception as e:
logger.error(f"视频上传失败: {str(e)}")
return Response({
"code": 500,
"message": f"视频上传失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=True, methods=['post'])
def manual_publish(self, request, pk=None):
"""手动发布视频"""
video = self.get_object()
# 检查视频状态是否允许发布
if video.status not in ['draft', 'scheduled']:
return Response({
"code": 400,
"message": f"当前视频状态为 {video.get_status_display()},无法发布",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
# 检查视频文件是否存在
if video.local_path and not os.path.exists(video.local_path):
return Response({
"code": 400,
"message": "视频文件不存在,无法发布",
"data": None
}, status=status.HTTP_400_BAD_REQUEST)
try:
# 获取视频URL如果没有提供则创建一个模拟的URL
video_url = request.data.get('video_url')
if not video_url:
# 创建模拟的视频URL和ID
platform_account = video.platform_account
platform_name = platform_account.platform_name
video_url = f"https://example.com/{platform_name}/{video.id}"
# 更新视频状态
video.status = 'published'
video.publish_time = timezone.now()
video.video_url = video_url
video.video_id = f"VID_{video.id}"
video.save()
logger.info(f"视频 {video.id} 已手动发布")
return Response({
"code": 200,
"message": "视频发布成功",
"data": {
"id": video.id,
"title": video.title,
"status": "published",
"video_url": video_url,
"publish_time": video.publish_time.strftime("%Y-%m-%d %H:%M:%S")
}
})
except Exception as e:
logger.error(f"手动发布视频失败: {str(e)}")
return Response({
"code": 500,
"message": f"发布失败: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)