first commit
This commit is contained in:
69
WebAdmin/AccessLog.py
Executable file
69
WebAdmin/AccessLog.py
Executable file
@@ -0,0 +1,69 @@
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from datetime import datetime
|
||||
from .models import WebsiteAccessLog, WebsiteInfo
|
||||
from .utils import (
|
||||
get_client_ip,
|
||||
get_browser_language,
|
||||
get_referrer,
|
||||
get_user_agent,
|
||||
get_device_type,
|
||||
get_beijing_time,
|
||||
)
|
||||
|
||||
@csrf_exempt
|
||||
def website_info_view(request):
|
||||
"""
|
||||
提供网站配置信息,并记录访问日志
|
||||
"""
|
||||
print("当前 UTC 时间:", datetime.now())
|
||||
|
||||
try:
|
||||
# 获取访问信息
|
||||
client_ip = get_client_ip(request)
|
||||
browser_language = get_browser_language(request)
|
||||
referrer = get_referrer(request)
|
||||
request_path = request.path
|
||||
request_method = request.method
|
||||
user_agent = get_user_agent(request)
|
||||
device_type = get_device_type(user_agent)
|
||||
|
||||
# 获取当前 UTC 时间并转换为北京时间
|
||||
access_time_bj = datetime.now()
|
||||
access_date = datetime.now()
|
||||
|
||||
# 检查同一 IP 当天是否已经记录过
|
||||
if not WebsiteAccessLog.objects.filter(ip_address=client_ip, access_date=access_date).exists():
|
||||
# 记录访问日志
|
||||
WebsiteAccessLog.objects.create(
|
||||
ip_address=client_ip,
|
||||
browser_language=browser_language,
|
||||
referrer=referrer,
|
||||
request_path=request_path,
|
||||
request_method=request_method,
|
||||
user_agent=user_agent,
|
||||
device_type=device_type,
|
||||
access_time_bj=access_time_bj,
|
||||
access_date=access_date,
|
||||
)
|
||||
|
||||
# 获取网站配置信息
|
||||
website_info = WebsiteInfo.objects.first()
|
||||
if not website_info:
|
||||
return JsonResponse({'code': 404, 'message': '未找到网站配置信息'})
|
||||
|
||||
# 返回网站配置信息
|
||||
data = {
|
||||
'domain_en': website_info.domain_en,
|
||||
'title_en': website_info.title_en,
|
||||
'keywords_en': website_info.keywords_en,
|
||||
'description_en': website_info.description_en,
|
||||
'domain_zh': website_info.domain_zh,
|
||||
'title_zh': website_info.title_zh,
|
||||
'keywords_zh': website_info.keywords_zh,
|
||||
'description_zh': website_info.description_zh,
|
||||
}
|
||||
return JsonResponse({'code': 200, 'message': 'ok', 'data': data})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': 500, 'message': f'服务器内部错误: {str(e)}'})
|
||||
0
WebAdmin/__init__.py
Executable file
0
WebAdmin/__init__.py
Executable file
BIN
WebAdmin/__pycache__/AccessLog.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/AccessLog.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/AccessLog.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/AccessLog.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/__init__.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/__init__.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/admin.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/admin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin_views.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/admin_views.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin_views.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/admin_views.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/ali_pay.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/ali_pay.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/ali_pay.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/ali_pay.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/api.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/api.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/api.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/apps.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/apps.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/apps.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/apps.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/audio.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/audio.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/audio.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/audio.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/base.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/base.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/base.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_music_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_music_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_music_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_music_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/mail.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/mail.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/mail.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/mail.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/middleware.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/middleware.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/models.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/models.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/models.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_payment.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/paypal_payment.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_payment.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/paypal_payment.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/signals.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/signals.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/signals.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/signals.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/task.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/task.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/task.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/task.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/task_all.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/task_all.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/urls.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/urls.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/urls.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/user.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/user.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/user.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/user.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/utils.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/utils.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/utils.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/views.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/views.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/views.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/views.cpython-311.pyc
Normal file
Binary file not shown.
13
WebAdmin/admin.py
Executable file
13
WebAdmin/admin.py
Executable file
@@ -0,0 +1,13 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from .models import User
|
||||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
model = User
|
||||
fieldsets = UserAdmin.fieldsets + (
|
||||
(None, {'fields': ('phone', 'google_id', 'is_member', 'points', 'invited_by', 'referral_code', 'commission_rate', 'login_count', 'last_login_ip')}),
|
||||
)
|
||||
list_display = ('id', 'username', 'email', 'phone', 'is_member', 'points', 'referral_code', 'commission_rate', 'login_count', 'last_login_ip') # 显示 id 字段
|
||||
search_fields = ('username', 'email', 'phone', 'referral_code')
|
||||
|
||||
admin.site.register(User, CustomUserAdmin)
|
||||
37
WebAdmin/admin_views.py
Executable file
37
WebAdmin/admin_views.py
Executable file
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
import re
|
||||
from django.contrib.auth import authenticate, login as auth_login
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from allauth.account.auth_backends import AuthenticationBackend
|
||||
from .models import User
|
||||
|
||||
@csrf_exempt
|
||||
def admin_login_view(request):
|
||||
if request.user.is_authenticated:
|
||||
return redirect('admin_home')
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
body = json.loads(request.body)
|
||||
username = body.get('username')
|
||||
password = body.get('password')
|
||||
user = authenticate(request, username=username, password=password)
|
||||
if user is not None:
|
||||
user.backend = 'allauth.account.auth_backends.AuthenticationBackend'
|
||||
auth_login(request, user)
|
||||
return JsonResponse({'code': 200, 'message': '登录成功'})
|
||||
else:
|
||||
return JsonResponse({'code': 400, 'message': '用户名或密码错误'})
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'code': 400, 'message': '无效的请求数据'})
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': 500, 'message': f'服务器内部错误: {str(e)}'})
|
||||
|
||||
return render(request, 'admin/admin_login.html')
|
||||
|
||||
@login_required
|
||||
def admin_home_view(request):
|
||||
return render(request, 'admin/home.html')
|
||||
331
WebAdmin/ali_pay.py
Executable file
331
WebAdmin/ali_pay.py
Executable file
@@ -0,0 +1,331 @@
|
||||
import json
|
||||
import traceback
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import JsonResponse
|
||||
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
|
||||
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
|
||||
from alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModel
|
||||
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
|
||||
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
|
||||
from alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequest
|
||||
from .models import Order, User, Plan
|
||||
from datetime import datetime
|
||||
from alipay.aop.api.domain.AlipayTradeWapPayModel import AlipayTradeWapPayModel
|
||||
from alipay.aop.api.request.AlipayTradeWapPayRequest import AlipayTradeWapPayRequest
|
||||
import requests
|
||||
from decimal import Decimal, getcontext
|
||||
|
||||
# 设置 Decimal 的精度
|
||||
getcontext().prec = 28
|
||||
|
||||
|
||||
def get_usd_to_cny_rate():
|
||||
a = 7.2
|
||||
return a
|
||||
|
||||
|
||||
|
||||
|
||||
def get_alipay_client_config(client_type='pc'):
|
||||
"""
|
||||
根据环境和客户端类型返回支付宝配置
|
||||
"""
|
||||
alipay_client_config = AlipayClientConfig()
|
||||
|
||||
if settings.ALIPAY_DEBUG:
|
||||
print('请求沙箱支付')
|
||||
alipay_client_config.server_url = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
|
||||
if client_type == 'pc':
|
||||
alipay_client_config.app_id = settings.ALIPAY_APP_ID_SANDBOX_PC
|
||||
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_SANDBOX_PC
|
||||
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_SANDBOX_PC
|
||||
elif client_type == 'mobile':
|
||||
alipay_client_config.app_id = settings.ALIPAY_APP_ID_SANDBOX_MOBILE
|
||||
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_SANDBOX_MOBILE
|
||||
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_SANDBOX_MOBILE
|
||||
else:
|
||||
print('请求生产支付')
|
||||
alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'
|
||||
if client_type == 'pc':
|
||||
alipay_client_config.app_id = settings.ALIPAY_APP_ID_PRODUCTION_PC
|
||||
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_PRODUCTION_PC
|
||||
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_PRODUCTION_PC
|
||||
elif client_type == 'mobile':
|
||||
alipay_client_config.app_id = settings.ALIPAY_APP_ID_PRODUCTION_MOBILE
|
||||
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_PRODUCTION_MOBILE
|
||||
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_PRODUCTION_MOBILE
|
||||
|
||||
return alipay_client_config
|
||||
def convert_cny_to_usd(price_cny):
|
||||
"""
|
||||
将人民币价格转换为美元价格
|
||||
:param price_cny: 人民币价格
|
||||
:return: 美元价格,保留两位小数
|
||||
"""
|
||||
# 获取美元对人民币的汇率
|
||||
usd_to_cny_rate = get_usd_to_cny_rate()
|
||||
print("原价:", price_cny)
|
||||
print("汇率:", usd_to_cny_rate)
|
||||
return (Decimal(price_cny) * Decimal(usd_to_cny_rate)).quantize(Decimal('0.00'))
|
||||
@csrf_exempt
|
||||
def create_alipay_order(request):
|
||||
"""
|
||||
创建支付宝订单并生成支付链接(电脑端)
|
||||
"""
|
||||
try:
|
||||
# 检查用户是否已登录
|
||||
if not request.user.is_authenticated:
|
||||
print("用户未登录")
|
||||
return JsonResponse({"code": 401, "message": "用户未登录"})
|
||||
|
||||
# 获取前端传递的数据
|
||||
data = json.loads(request.body)
|
||||
print(f"接收到的请求数据: {data}")
|
||||
plan_title = data.get('title')
|
||||
price = Decimal(data.get('price')) # 将价格转换为 Decimal 类型
|
||||
plan_id = data.get('id')
|
||||
|
||||
# 从数据库获取对应的套餐信息
|
||||
plan = Plan.objects.get(id=plan_id)
|
||||
print(f"数据库中获取的套餐信息: {plan}")
|
||||
if not plan:
|
||||
print("套餐信息不存在")
|
||||
return JsonResponse({"code": 400, "message": "套餐信息不存在"})
|
||||
|
||||
# 检查汇率是否有效
|
||||
# 获取美元对人民币的汇率
|
||||
usd_to_cny_rate = get_usd_to_cny_rate()
|
||||
if not usd_to_cny_rate:
|
||||
print("无法进行价格转换,汇率不可用")
|
||||
return JsonResponse({"code": 500, "message": "汇率不可用,无法创建订单"})
|
||||
|
||||
# 将套餐的人民币价格转换为美元价格
|
||||
plan_price_usd = convert_cny_to_usd(plan.price)
|
||||
|
||||
# 将人民币价格转换为美元价格
|
||||
price_usd = convert_cny_to_usd(price)
|
||||
|
||||
# 比较转换后的美元价格
|
||||
if not price_usd or plan_price_usd != price_usd:
|
||||
print(f"{plan_price_usd}套餐信息不匹配或价格不正确{price_usd}")
|
||||
return JsonResponse({"code": 400, "message": "套餐信息不匹配或价格不正确"})
|
||||
|
||||
final_price = price_usd
|
||||
|
||||
# 获取当前用户信息
|
||||
user = request.user
|
||||
|
||||
# 生成订单
|
||||
order_id = f"order_{datetime.now().strftime('%Y%m%d%H%M%S')}_{user.id}"
|
||||
order = Order.objects.create(
|
||||
order_id=order_id,
|
||||
user=user,
|
||||
plan=plan,
|
||||
username=user.username,
|
||||
amount=final_price,
|
||||
payment_method='alipay',
|
||||
status='pending'
|
||||
)
|
||||
print(f"创建的订单信息: {order}")
|
||||
|
||||
# 获取支付宝电脑端客户端配置
|
||||
alipay_client_config = get_alipay_client_config(client_type='pc')
|
||||
client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
|
||||
|
||||
# 构造支付宝支付请求对象
|
||||
model = AlipayTradePagePayModel()
|
||||
model.out_trade_no = order_id # 订单ID
|
||||
model.total_amount = str(final_price) # 订单金额
|
||||
model.subject = plan_title # 商品名称
|
||||
model.product_code = "FAST_INSTANT_TRADE_PAY" # 产品码
|
||||
print(f"生成订单请求体: {model}")
|
||||
|
||||
request = AlipayTradePagePayRequest(biz_model=model)
|
||||
request.notify_url = settings.ALIPAY_NOTIFY_URL # 异步通知URL
|
||||
request.return_url = settings.ALIPAY_RETURN_URL # 同步返回URL
|
||||
print(f"回调地址: {settings.ALIPAY_NOTIFY_URL}")
|
||||
|
||||
response = client.page_execute(request, http_method="GET")
|
||||
print(f"支付宝请求响应: {response}")
|
||||
|
||||
# 返回支付URL
|
||||
return JsonResponse({"code": 200, "message": "订单创建成功", "data": {'alipay_url': response}})
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建订单时出错: {traceback.format_exc()}")
|
||||
return JsonResponse({"code": 500, "message": f"创建订单时出错: {str(e)}"})
|
||||
|
||||
@csrf_exempt
|
||||
def create_alipay_h5_order(request):
|
||||
"""
|
||||
创建支付宝H5订单并生成支付链接
|
||||
"""
|
||||
try:
|
||||
# 检查用户是否已登录
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({"code": 401, "message": "用户未登录"})
|
||||
|
||||
# 获取前端传递的数据
|
||||
data = json.loads(request.body)
|
||||
plan_id = data.get('id')
|
||||
plan_title = data.get('title')
|
||||
price = Decimal(data.get('price')) # 将价格转换为 Decimal 类型
|
||||
|
||||
# 获取对应套餐信息
|
||||
plan = Plan.objects.get(id=plan_id)
|
||||
if not plan:
|
||||
return JsonResponse({"code": 400, "message": "套餐信息不存在"})
|
||||
|
||||
# 检查汇率是否有效
|
||||
# 获取美元对人民币的汇率
|
||||
usd_to_cny_rate = get_usd_to_cny_rate()
|
||||
if not usd_to_cny_rate:
|
||||
print("无法进行价格转换,汇率不可用")
|
||||
return JsonResponse({"code": 500, "message": "汇率不可用,无法创建订单"})
|
||||
|
||||
# 将套餐的人民币价格转换为美元价格
|
||||
plan_price_usd = convert_cny_to_usd(plan.price)
|
||||
|
||||
# 将人民币价格转换为美元价格
|
||||
price_usd = convert_cny_to_usd(price)
|
||||
|
||||
# 比较转换后的美元价格
|
||||
if not price_usd or plan_price_usd != price_usd:
|
||||
return JsonResponse({"code": 400, "message": "套餐信息不匹配或价格不正确"})
|
||||
|
||||
final_price = price_usd
|
||||
|
||||
# 创建订单
|
||||
user = request.user
|
||||
order_id = f"order_{datetime.now().strftime('%Y%m%d%H%M%S')}_{user.id}"
|
||||
order = Order.objects.create(
|
||||
order_id=order_id,
|
||||
user=user,
|
||||
plan=plan,
|
||||
username=user.username,
|
||||
amount=final_price,
|
||||
payment_method='alipay',
|
||||
status='pending'
|
||||
)
|
||||
|
||||
# 获取支付宝H5客户端配置
|
||||
alipay_client_config = get_alipay_client_config(client_type='mobile')
|
||||
client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
|
||||
|
||||
# 构造支付宝H5支付请求对象
|
||||
model = AlipayTradeWapPayModel()
|
||||
model.out_trade_no = order_id
|
||||
model.total_amount = str(final_price)
|
||||
model.subject = plan_title
|
||||
model.product_code = "QUICK_WAP_WAY"
|
||||
|
||||
request = AlipayTradeWapPayRequest(biz_model=model)
|
||||
request.notify_url = settings.ALIPAY_NOTIFY_URL # 异步通知URL
|
||||
request.return_url = settings.ALIPAY_RETURN_URL # 同步返回URL
|
||||
|
||||
# 执行请求,获取支付宝H5支付跳转链接
|
||||
response = client.page_execute(request, http_method="GET")
|
||||
|
||||
return JsonResponse({"code": 200, "message": "订单创建成功", "data": {'alipay_url': response}})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({"code": 500, "message": f"创建H5订单时出错: {str(e)}"})
|
||||
|
||||
|
||||
def update_user_membership(order):
|
||||
"""
|
||||
更新用户积分信息
|
||||
"""
|
||||
try:
|
||||
# 验证订单状态是否为成功
|
||||
if order.status not in ("completed", "TRADE_SUCCESS", "TRADE_FINISHED"):
|
||||
print("订单未成功")
|
||||
return False
|
||||
|
||||
# 从订单中获取关联的套餐信息
|
||||
plan = order.plan
|
||||
|
||||
# 检查汇率是否有效
|
||||
# 获取美元对人民币的汇率
|
||||
usd_to_cny_rate = get_usd_to_cny_rate()
|
||||
if not usd_to_cny_rate:
|
||||
print("无法进行价格转换,汇率不可用")
|
||||
return False
|
||||
|
||||
# 将订单金额从美元转换回人民币
|
||||
order_amount_cny = order.amount
|
||||
|
||||
# 确保订单的套餐与实际套餐详情匹配
|
||||
if not plan or convert_cny_to_usd(plan.price) != order_amount_cny:
|
||||
print(f"订单的套餐信息与实际套餐不匹配plan.price{plan.price}---")
|
||||
return False
|
||||
|
||||
# 获取用户信息
|
||||
user = order.user
|
||||
|
||||
# 更新用户的积分
|
||||
user.points += plan.credits_per_month
|
||||
user.save()
|
||||
|
||||
print(f"用户 {user.username} 的积分已更新")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"更新用户积分信息时出错: {e}")
|
||||
return False
|
||||
|
||||
@csrf_exempt
|
||||
def alipay_notify(request):
|
||||
"""
|
||||
支付宝异步通知处理,验证支付结果
|
||||
"""
|
||||
try:
|
||||
data = request.POST.dict()
|
||||
print(f"接收到的异步通知数据: {data}")
|
||||
|
||||
# 直接使用返回的数据处理逻辑
|
||||
order_id = data.get('out_trade_no')
|
||||
trade_status = data.get('trade_status')
|
||||
print(f"订单ID: {order_id}, 支付状态: {trade_status}")
|
||||
|
||||
try:
|
||||
order = Order.objects.get(order_id=order_id)
|
||||
except Order.DoesNotExist:
|
||||
print(f"订单 {order_id} 不存在")
|
||||
return JsonResponse({"code": 400, "message": "订单不存在"})
|
||||
|
||||
if trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
|
||||
order.status = 'completed'
|
||||
order.save()
|
||||
print(f"订单 {order_id} 支付成功")
|
||||
update_user_membership(order)
|
||||
return JsonResponse({"code": 200, "message": "支付成功"})
|
||||
else:
|
||||
order.status = 'failed'
|
||||
order.save()
|
||||
print(f"订单 {order_id} 支付失败或未成功")
|
||||
return JsonResponse({"code": 400, "message": "支付未成功或失败"})
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理异步通知时出错: {traceback.format_exc()}")
|
||||
return JsonResponse({"code": 500, "message": f"处理异步通知时出错: {str(e)}"})
|
||||
|
||||
@csrf_exempt
|
||||
def alipay_return(request):
|
||||
"""
|
||||
支付宝同步返回处理,用于用户支付后的页面跳转
|
||||
"""
|
||||
try:
|
||||
data = request.GET.dict()
|
||||
print(f"接收到的同步返回数据: {data}")
|
||||
|
||||
# 直接使用返回的数据处理逻辑
|
||||
order_id = data.get('out_trade_no')
|
||||
print(f"订单 {order_id} 支付成功")
|
||||
return JsonResponse({"code": 200, "message": f"支付成功,订单号:{order_id}"})
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理同步返回时出错: {traceback.format_exc()}")
|
||||
return JsonResponse({"code": 500, "message": f"处理同步返回时出错: {str(e)}"})
|
||||
63
WebAdmin/aliyun_sms.py
Executable file
63
WebAdmin/aliyun_sms.py
Executable file
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import json
|
||||
import hashlib
|
||||
import hmac
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
|
||||
from alibabacloud_tea_util import models as util_models
|
||||
from alibabacloud_tea_util.client import Client as UtilClient
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
from WebSite import settings
|
||||
from .models import SMSVerificationCode
|
||||
|
||||
def create_client() -> Dysmsapi20170525Client:
|
||||
config = open_api_models.Config(
|
||||
access_key_id=settings.ALIBABA_CLOUD_ACCESS_KEY_ID,
|
||||
access_key_secret=settings.ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
||||
)
|
||||
config.endpoint = 'dysmsapi.aliyuncs.com'
|
||||
return Dysmsapi20170525Client(config)
|
||||
|
||||
def send_verification_sms(phone_number: str, sign_name: str, template_code: str) -> dict:
|
||||
client = create_client()
|
||||
code = get_random_string(length=6, allowed_chars='0123456789')
|
||||
template_param = json.dumps({"code": code})
|
||||
|
||||
send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
|
||||
phone_numbers=phone_number,
|
||||
sign_name=sign_name,
|
||||
template_code=template_code,
|
||||
template_param=template_param
|
||||
)
|
||||
runtime = util_models.RuntimeOptions()
|
||||
try:
|
||||
response = client.send_sms_with_options(send_sms_request, runtime)
|
||||
response_data = response.body.to_map()
|
||||
|
||||
if response_data.get("Code") == "OK":
|
||||
SMSVerificationCode.objects.create(phone_number=phone_number, code=code, created_at=datetime.now())
|
||||
return {'code': 200, 'message': '验证码发送成功'}
|
||||
else:
|
||||
return {'code': 400, 'message': f'发送短信失败: {response_data.get("Message")}'}
|
||||
except Exception as error:
|
||||
return {'code': 500, 'message': f'发送短信异常: {str(error)}'}
|
||||
|
||||
def verify_sms_code(phone_number: str, code: str) -> dict:
|
||||
try:
|
||||
sms_code = SMSVerificationCode.objects.filter(phone_number=phone_number, code=code, is_used=False).latest('created_at')
|
||||
if sms_code is None:
|
||||
return {'code': 400, 'message': '请先获取验证码'}
|
||||
|
||||
if sms_code.created_at < datetime.now() - timedelta(minutes=5):
|
||||
return {'code': 400, 'message': '验证码已过期'}
|
||||
|
||||
sms_code.is_used = True
|
||||
sms_code.save()
|
||||
return {'code': 200, 'message': '验证码验证成功'}
|
||||
except SMSVerificationCode.DoesNotExist:
|
||||
return {'code': 400, 'message': '验证码无效或已过期'}
|
||||
500
WebAdmin/api.py
Executable file
500
WebAdmin/api.py
Executable file
@@ -0,0 +1,500 @@
|
||||
import time
|
||||
|
||||
import requests
|
||||
import json
|
||||
import uuid
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from datetime import datetime
|
||||
from .create_tiktok_video import create_video
|
||||
from .create_avatar_video import create_avatar_video_payload
|
||||
from .create_music_video import create_music_video_payload
|
||||
from .models import VideoGeneration, User, Plan, Order, WebsiteInfo
|
||||
from .base import logger,synthesize_speech,upload_avatar,deduct_points
|
||||
|
||||
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
|
||||
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
|
||||
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
|
||||
|
||||
def generate_video_id(user_id):
|
||||
timestamp = int(time.time())
|
||||
video_id = f"{timestamp}_{user_id}"
|
||||
return video_id
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def handle_generate_tiktok_video_request(request):
|
||||
"""
|
||||
处理生成 create-tiktok-video 视频请求的接口
|
||||
:param request: HTTP 请求对象
|
||||
:return: 包含生成状态或错误信息的JSON响应
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
|
||||
data = json.loads(request.body)
|
||||
text = data.get('text', '')
|
||||
voice_name = data.get('voice', '')
|
||||
style = data.get('style', 'general')
|
||||
rate = data.get('rate', 0)
|
||||
media_type = data.get('mediaType', '')
|
||||
ratio = data.get('ratio', '')
|
||||
slug = data.get('slug', 'create-tiktok-video')
|
||||
caption_preset = data.get('captionPreset', 'Wrap 1')
|
||||
caption_position = data.get('captionPosition', 'bottom')
|
||||
disableCaptions = data.get('disableCaptions',True)
|
||||
generationPreset = data.get('generationPreset','LEONARDO') #ANIME
|
||||
if not all([text, voice_name, media_type, ratio, slug, caption_preset, caption_position]):
|
||||
return JsonResponse({'code': 400, 'message': '缺少必要参数'})
|
||||
|
||||
user = User.objects.get(id=request.user.id)
|
||||
|
||||
# 计算所需积分
|
||||
result = deduct_points(user, "create-tiktok-video", text=text)
|
||||
if result is not True:
|
||||
return result # 返回积分不足的响应
|
||||
|
||||
video_gen = VideoGeneration.objects.create(
|
||||
user=user,
|
||||
text=text,
|
||||
voice_name=voice_name,
|
||||
style=style,
|
||||
rate=rate,
|
||||
media_type=media_type,
|
||||
ratio=ratio,
|
||||
slug=slug,
|
||||
video_id=generate_video_id(user.id)
|
||||
)
|
||||
|
||||
audio_result = synthesize_speech(text, voice_name, style, rate)
|
||||
if audio_result['code'] != 200:
|
||||
video_gen.status = 'failed'
|
||||
video_gen.save()
|
||||
return JsonResponse(audio_result)
|
||||
|
||||
audio_url = audio_result['audio_url']
|
||||
video_gen.audio_url = audio_url
|
||||
video_gen.status = 'in_progress'
|
||||
video_gen.save()
|
||||
|
||||
video_result = create_video(
|
||||
audio_url, generationPreset,media_type, text, ratio, voice_name, user.id, slug,disableCaptions, caption_preset, caption_position, video_id=video_gen.video_id
|
||||
)
|
||||
if video_result['code'] == 200:
|
||||
video_gen.status = 'Pending'
|
||||
video_gen.pid = video_result['data']['pid']
|
||||
video_gen.save()
|
||||
return JsonResponse(video_result)
|
||||
else:
|
||||
video_gen.status = 'Failed'
|
||||
video_gen.save()
|
||||
return JsonResponse({"code": 405, "message": "生成失败"})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({"code": 400, "message": "无效的JSON数据"})
|
||||
except Exception as e:
|
||||
logger.error(f"处理视频生成请求时出错: {str(e)}")
|
||||
return JsonResponse({"code": 500, "message": str(e)})
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def handle_generate_avatar_video_request(request):
|
||||
"""
|
||||
处理生成 create-avatar-video 视频请求的接口
|
||||
:param request: HTTP 请求对象
|
||||
:return: 包含生成状态或错误信息的JSON响应
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
|
||||
data = json.loads(request.body)
|
||||
text = data.get('text', '')
|
||||
voice_name = data.get('voice', '')
|
||||
style = data.get('style', 'general')
|
||||
rate = data.get('rate', 0)
|
||||
media_type = data.get('mediaType', '')
|
||||
ratio = data.get('ratio', '9 / 16')
|
||||
slug = 'create-avatar-video'
|
||||
caption_preset = data.get('captionPreset', 'Wrap 1')
|
||||
caption_position = data.get('captionPosition', 'bottom')
|
||||
selected_avatar = data.get('selectedAvatar', '')
|
||||
disableCaptions = data.get('disableCaptions',True)
|
||||
generationPreset = data.get('generationPreset','LEONARDO') #ANIME
|
||||
if not all([text, voice_name, media_type, ratio, slug, caption_preset, caption_position, selected_avatar]):
|
||||
return JsonResponse({'code': 400, 'message': '缺少必要参数'})
|
||||
|
||||
user = User.objects.get(id=request.user.id)
|
||||
|
||||
# 计算所需积分
|
||||
result = deduct_points(user, "create-avatar-video", text=text)
|
||||
if result is not True:
|
||||
return result # 返回积分不足的响应
|
||||
|
||||
video_gen = VideoGeneration.objects.create(
|
||||
user=user,
|
||||
text=text,
|
||||
voice_name=voice_name,
|
||||
style=style,
|
||||
rate=rate,
|
||||
media_type=media_type,
|
||||
ratio=ratio,
|
||||
slug=slug,
|
||||
video_id=generate_video_id(user.id)
|
||||
)
|
||||
|
||||
audio_result = synthesize_speech(text, voice_name, style, rate)
|
||||
if audio_result['code'] != 200:
|
||||
video_gen.status = 'failed'
|
||||
video_gen.save()
|
||||
return JsonResponse(audio_result)
|
||||
|
||||
audio_url = audio_result['audio_url']
|
||||
video_gen.audio_url = audio_url
|
||||
video_gen.status = 'in_progress'
|
||||
video_gen.save()
|
||||
|
||||
video_result = create_avatar_video_payload(
|
||||
audio_url,generationPreset, media_type, text, ratio, voice_name, user.id, slug,disableCaptions, caption_preset, caption_position, selected_avatar, video_id=video_gen.video_id
|
||||
)
|
||||
if video_result['code'] == 200:
|
||||
video_gen.status = 'Pending'
|
||||
video_gen.pid = video_result['data']['pid']
|
||||
video_gen.save()
|
||||
return JsonResponse(video_result)
|
||||
else:
|
||||
video_gen.status = 'Failed'
|
||||
video_gen.save()
|
||||
return JsonResponse({"code": 405, "message": "生成失败"})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({"code": 400, "message": "无效的JSON数据"})
|
||||
except Exception as e:
|
||||
logger.error(f"处理头像视频生成请求时出错: {str(e)}")
|
||||
return JsonResponse({"code": 500, "message": str(e)})
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def handle_generate_music_video_request(request):
|
||||
"""
|
||||
处理生成 create-music-video 视频请求的接口
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
|
||||
data = json.loads(request.body)
|
||||
audio_url = data.get('audioUrl', '')
|
||||
media_type = data.get('mediaType', 'stockVideo')
|
||||
ratio = data.get('ratio', '9 / 16')
|
||||
slug = data.get('slug', 'create-music-video')
|
||||
caption_preset = data.get('captionPreset', 'Wrap 1')
|
||||
caption_position = data.get('captionPosition', 'bottom')
|
||||
disableCaptions = data.get('disableCaptions',True)
|
||||
generationPreset = data.get('generationPreset','LEONARDO') #ANIME
|
||||
if not all([audio_url, media_type, ratio, slug, caption_preset, caption_position]):
|
||||
return JsonResponse({'code': 400, 'message': '缺少必要参数'})
|
||||
|
||||
user = User.objects.get(id=request.user.id)
|
||||
|
||||
result = deduct_points(user, "create-music-video")
|
||||
if result is not True:
|
||||
return result # 返回积分不足的响应
|
||||
|
||||
video_gen = VideoGeneration.objects.create(
|
||||
user=user,
|
||||
text='',
|
||||
voice_name='',
|
||||
style='',
|
||||
rate=0,
|
||||
media_type=media_type,
|
||||
ratio=ratio,
|
||||
slug=slug,
|
||||
video_id=generate_video_id(user.id)
|
||||
)
|
||||
|
||||
video_result = create_music_video_payload(
|
||||
audio_url=audio_url,
|
||||
generationPreset=generationPreset,
|
||||
media_type=media_type,
|
||||
text='',
|
||||
ratio=ratio,
|
||||
voice_name='',
|
||||
style='',
|
||||
rate=0,
|
||||
user_id=user.id,
|
||||
slug=slug,
|
||||
disableCaptions=disableCaptions,
|
||||
caption_preset=caption_preset,
|
||||
caption_position=caption_position,
|
||||
video_id=video_gen.video_id
|
||||
)
|
||||
print(video_result)
|
||||
if video_result['code'] == 200:
|
||||
video_gen.status = 'Pending'
|
||||
video_gen.pid = video_result['data']['pid']
|
||||
video_gen.save()
|
||||
return JsonResponse(video_result)
|
||||
else:
|
||||
video_gen.status = 'Failed'
|
||||
video_gen.save()
|
||||
return JsonResponse({"code": 405, "message": "生成失败"})
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({"code": 400, "message": "无效的JSON数据"})
|
||||
except Exception as e:
|
||||
logger.error(f"处理音乐视频生成请求时出错: {str(e)}")
|
||||
return JsonResponse({"code": 500, "message": str(e)})
|
||||
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webhook(request):
|
||||
"""
|
||||
处理生成视频回调的函数
|
||||
"""
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
# 获取原始的 POST 数据
|
||||
raw_data = request.body
|
||||
logger.info("Received raw data:\n%s\n", raw_data)
|
||||
|
||||
# 解析 JSON 数据
|
||||
data = json.loads(raw_data)
|
||||
if not data:
|
||||
return HttpResponse(status=400) # Bad Request
|
||||
|
||||
# 记录完整的接收到的数据
|
||||
logger.info("Received complete callback data:\n%s\n", json.dumps(data, indent=4))
|
||||
|
||||
# 提取基本信息
|
||||
video_url = data.get('videoUrl', '') # 编辑视频 URL
|
||||
video_id = data.get('videoId', '') # 视频 ID
|
||||
user_id = request.GET.get('userid', '')
|
||||
|
||||
if user_id and video_id:
|
||||
print('回调更新状态')
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
video_gen = VideoGeneration.objects.filter(user=user, video_id=video_id).first()
|
||||
if video_gen:
|
||||
if video_url:
|
||||
video_gen.video_url = video_url
|
||||
video_gen.status = 'completed'
|
||||
else:
|
||||
video_gen.status = 'failed'
|
||||
video_gen.save()
|
||||
|
||||
# 构建输出数据
|
||||
output = {
|
||||
'videoUrl': video_url,
|
||||
'status': video_gen.status
|
||||
}
|
||||
|
||||
# 记录基本信息
|
||||
logger.info("Updated video generation status and video URL:\n%s\n", json.dumps(output, indent=4))
|
||||
else:
|
||||
logger.warning(f"No video generation found for user_id: {user_id} and video_id: {video_id}")
|
||||
except ObjectDoesNotExist:
|
||||
logger.error(f"User with id {user_id} does not exist")
|
||||
else:
|
||||
logger.error("User ID or Video ID not provided in the webhook URL or callback data")
|
||||
|
||||
return HttpResponse(status=200)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("Invalid JSON data: %s", str(e))
|
||||
return HttpResponse(status=400) # Bad Request
|
||||
except Exception as e:
|
||||
logger.error("Error: %s", str(e))
|
||||
return HttpResponse(status=500) # Internal Server Error
|
||||
else:
|
||||
return HttpResponse(status=405) # Method Not Allowed
|
||||
|
||||
|
||||
#获取视频列表
|
||||
@csrf_exempt
|
||||
@require_http_methods(["GET"])
|
||||
@login_required
|
||||
def get_video_list(request):
|
||||
"""
|
||||
获取视频列表的函数,支持分页
|
||||
"""
|
||||
try:
|
||||
user = request.user
|
||||
|
||||
# 检查是否有用户ID参数
|
||||
user_id = request.GET.get('user_id', None)
|
||||
|
||||
# 获取分页参数
|
||||
page = request.GET.get('page', 1)
|
||||
page_size = request.GET.get('page_size', 10)
|
||||
|
||||
# 如果有user_id参数,并且当前用户是管理员,则获取该用户的视频列表
|
||||
if user_id and user.is_staff:
|
||||
try:
|
||||
specific_user = User.objects.get(id=user_id)
|
||||
video_list = VideoGeneration.objects.filter(user=specific_user).order_by('-created_at')
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse({'code': 404, 'message': '用户不存在'}, status=404)
|
||||
# 如果没有user_id参数或者当前用户不是管理员,则获取当前用户的视频列表
|
||||
elif not user.is_staff:
|
||||
video_list = VideoGeneration.objects.filter(user=user).order_by('-created_at')
|
||||
else:
|
||||
# 管理员返回所有记录
|
||||
video_list = VideoGeneration.objects.all().order_by('-created_at')
|
||||
|
||||
# 使用Paginator进行分页
|
||||
paginator = Paginator(video_list, page_size)
|
||||
|
||||
try:
|
||||
videos = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
videos = paginator.page(1)
|
||||
except EmptyPage:
|
||||
videos = paginator.page(paginator.num_pages)
|
||||
|
||||
video_data = []
|
||||
for video in videos:
|
||||
video_data.append({
|
||||
'id': video.id,
|
||||
'text': video.text,
|
||||
'voice_name': video.voice_name,
|
||||
'style': video.style,
|
||||
'rate': video.rate,
|
||||
'media_type': video.media_type,
|
||||
'ratio': video.ratio,
|
||||
'audio_url': video.audio_url,
|
||||
'video_url': video.video_url,
|
||||
'status': video.status,
|
||||
'created_at': video.created_at,
|
||||
'updated_at': video.updated_at,
|
||||
'pid':video.pid,
|
||||
'video_id':video.video_id,
|
||||
'slug': video.slug,
|
||||
})
|
||||
|
||||
response_data = {
|
||||
'code': 200,
|
||||
'data': {"list":video_data,'page': videos.number,
|
||||
'page_size': paginator.per_page,
|
||||
'total_pages': paginator.num_pages,
|
||||
'total_videos': paginator.count}
|
||||
}
|
||||
|
||||
return JsonResponse(response_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching video list: {str(e)}")
|
||||
return JsonResponse({"code": 500, "message": str(e)}, status=500)
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def get_plans(request):
|
||||
"""
|
||||
获取所有套餐的API接口,包含code和message字段
|
||||
"""
|
||||
try:
|
||||
# 查询所有套餐
|
||||
plans = Plan.objects.all()
|
||||
|
||||
# 构建响应数据
|
||||
plans_data = [
|
||||
{ "id": plan.id,
|
||||
"title": plan.title,
|
||||
"description": plan.description,
|
||||
"price": float(plan.price), # 将Decimal类型转换为float类型
|
||||
"credits_per_month": plan.credits_per_month,
|
||||
"created_at": plan.created_at,
|
||||
"updated_at": plan.updated_at,
|
||||
"is_promotional": plan.is_promotional,
|
||||
"unlimited_exports": plan.unlimited_exports,
|
||||
"smart_music_sync": plan.smart_music_sync
|
||||
}
|
||||
for plan in plans
|
||||
]
|
||||
|
||||
# 返回包含code和message的JSON响应
|
||||
return JsonResponse({"code": 200, "message": "获取成功", "data": plans_data}, status=200)
|
||||
except Exception as e:
|
||||
# 处理异常并返回错误信息
|
||||
return JsonResponse({"code": 500, "message": f"服务器错误: {str(e)}"}, status=500)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["GET"])
|
||||
def get_orders(request):
|
||||
"""
|
||||
获取订单列表,支持分页、状态筛选和排序
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
status = request.GET.get('status', None)
|
||||
page = request.GET.get('page', 1)
|
||||
page_size = request.GET.get('page_size', 10)
|
||||
|
||||
# 过滤订单状态
|
||||
if status:
|
||||
orders = Order.objects.filter(status=status).order_by('-created_at')
|
||||
else:
|
||||
orders = Order.objects.all().order_by('-created_at')
|
||||
|
||||
# 分页
|
||||
paginator = Paginator(orders, page_size)
|
||||
try:
|
||||
orders_page = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
orders_page = paginator.page(1)
|
||||
except EmptyPage:
|
||||
orders_page = paginator.page(paginator.num_pages)
|
||||
|
||||
# 构建响应数据
|
||||
order_list = [
|
||||
{
|
||||
'order_id': order.order_id,
|
||||
'user_id': order.user.id,
|
||||
'username': order.user.username,
|
||||
'amount': float(order.amount),
|
||||
'payment_method': order.payment_method,
|
||||
'status': order.status,
|
||||
'created_at': order.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'updated_at': order.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
}
|
||||
for order in orders_page
|
||||
]
|
||||
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': order_list,
|
||||
'page': orders_page.number,
|
||||
'total_pages': paginator.num_pages,
|
||||
'total_orders': paginator.count,
|
||||
})
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': 500, 'message': str(e)})
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["DELETE"])
|
||||
def delete_order(request, order_id):
|
||||
"""
|
||||
删除指定订单
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
order = Order.objects.get(order_id=order_id)
|
||||
order.delete()
|
||||
return JsonResponse({'code': 200, 'message': '订单删除成功'})
|
||||
except Order.DoesNotExist:
|
||||
return JsonResponse({'code': 404, 'message': '订单不存在'})
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': 500, 'message': str(e)})
|
||||
|
||||
|
||||
8
WebAdmin/apps.py
Executable file
8
WebAdmin/apps.py
Executable file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class WebadminConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "WebAdmin"
|
||||
|
||||
def ready(self):
|
||||
import WebAdmin.signals # 确保在应用启动时加载信号处理程序
|
||||
1236
WebAdmin/audio.py
Executable file
1236
WebAdmin/audio.py
Executable file
File diff suppressed because it is too large
Load Diff
239
WebAdmin/base.py
Executable file
239
WebAdmin/base.py
Executable file
@@ -0,0 +1,239 @@
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.http import JsonResponse
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from azure.storage.blob import BlobServiceClient, BlobClient
|
||||
|
||||
from .models import VideoGeneration, User, TransactionHistory
|
||||
from .utils import get_logger
|
||||
|
||||
# 配置日志记录
|
||||
log_file = 'API日志.log'
|
||||
logger = get_logger('API日志', log_file, when='midnight', backup_count=7)
|
||||
|
||||
# API 配置
|
||||
API_KEY = 'be13f9fbf56446bdbaa16e986bb0771b'
|
||||
REGION = 'eastus'
|
||||
ENDPOINT = f'https://{REGION}.tts.speech.microsoft.com/cognitiveservices/v1'
|
||||
|
||||
@csrf_exempt
|
||||
def synthesize_speech(text, voice_name, style='general', rate=0):
|
||||
"""
|
||||
生成语音文件并返回音频URL
|
||||
"""
|
||||
try:
|
||||
# 提取语言信息
|
||||
language = voice_name.split('-')[0] + '-' + voice_name.split('-')[1]
|
||||
rate_str = f"{rate}%"
|
||||
output_format = 'audio-16khz-32kbitrate-mono-mp3'
|
||||
|
||||
# 设置请求头
|
||||
headers = {
|
||||
'Ocp-Apim-Subscription-Key': API_KEY,
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': output_format,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0;'
|
||||
}
|
||||
|
||||
# 构建SSML请求体
|
||||
ssml = f"""
|
||||
<speak version='1.0' xmlns:mstts='http://www.w3.org/2001/mstts' xml:lang='{language}'>
|
||||
<voice xml:lang='{language}' xml:gender='Female' name='{voice_name}'>
|
||||
<mstts:express-as style='{style}'>
|
||||
<prosody rate='{rate_str}'>{text}</prosody>
|
||||
</mstts:express-as>
|
||||
</voice>
|
||||
</speak>
|
||||
"""
|
||||
|
||||
# 发送请求到 Microsoft 语音合成API
|
||||
response = requests.post(ENDPOINT, headers=headers, data=ssml)
|
||||
if response.status_code == 200:
|
||||
# 保存音频文件
|
||||
audio_filename = f"{uuid.uuid4()}.mp3"
|
||||
audio_path = os.path.join(settings.MEDIA_ROOT, audio_filename)
|
||||
with open(audio_path, 'wb') as audio_file:
|
||||
audio_file.write(response.content)
|
||||
audio_url = f"{settings.DOMAIN}{settings.MEDIA_URL}{audio_filename}"
|
||||
return {"code": 200, "audio_url": audio_url}
|
||||
else:
|
||||
return {"code": response.status_code, "message": "无法合成语音"}
|
||||
except Exception as e:
|
||||
return {"code": 500, "message": str(e)}
|
||||
|
||||
# 上传图片的接口
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def upload_avatar(request):
|
||||
"""
|
||||
上传图片的接口,返回图片URL
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
|
||||
if 'avatar' not in request.FILES:
|
||||
return JsonResponse({'code': 400, 'message': '没有上传图片'})
|
||||
|
||||
avatar_file = request.FILES['avatar']
|
||||
|
||||
# 保存图片到指定目录 avatars/
|
||||
avatar_filename = f"{uuid.uuid4()}.{avatar_file.name.split('.')[-1]}"
|
||||
avatar_path = os.path.join(settings.MEDIA_ROOT, 'avatars', avatar_filename)
|
||||
with open(avatar_path, 'wb') as f:
|
||||
for chunk in avatar_file.chunks():
|
||||
f.write(chunk)
|
||||
|
||||
avatar_url = f"{settings.DOMAIN}{settings.MEDIA_URL}avatars/{avatar_filename}"
|
||||
|
||||
return JsonResponse({'code': 200, "message": "上传成功",'data':{'avatar_url': avatar_url} })
|
||||
except Exception as e:
|
||||
logger.error(f"上传头像时出错: {str(e)}")
|
||||
return JsonResponse({"code": 500, "message": str(e)})
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST"])
|
||||
def upload_audio(request):
|
||||
"""
|
||||
上传音乐或视频文件的接口,返回文件URL(支持 .m4v, .mp4, .mp3, .wav 格式)
|
||||
"""
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
|
||||
if 'audio' not in request.FILES:
|
||||
return JsonResponse({'code': 400, 'message': '没有上传音频或视频文件'})
|
||||
|
||||
audio_file = request.FILES['audio']
|
||||
file_extension = audio_file.name.split('.')[-1].lower()
|
||||
|
||||
# 允许的文件格式
|
||||
allowed_extensions = ['m4v', 'mp4', 'mp3', 'wav']
|
||||
|
||||
# 检查文件格式是否被允许
|
||||
if file_extension not in allowed_extensions:
|
||||
return JsonResponse({'code': 400, 'message': '只支持 .m4v, .mp4, .mp3, .wav 格式的文件'})
|
||||
|
||||
# 生成唯一的文件名
|
||||
audio_filename = f"{uuid.uuid4()}.{file_extension}"
|
||||
|
||||
# 创建 Azure BlobServiceClient 实例
|
||||
blob_service_client = BlobServiceClient.from_connection_string(settings.AZURE_CONNECTION_STRING)
|
||||
container_client = blob_service_client.get_container_client(settings.AZURE_CONTAINER_NAME)
|
||||
|
||||
# 上传文件到 Azure Blob Storage
|
||||
blob_client = container_client.get_blob_client(audio_filename)
|
||||
|
||||
# 使用 File 的 read 方法直接上传文件
|
||||
blob_client.upload_blob(audio_file, overwrite=True)
|
||||
|
||||
# 获取文件的 URL
|
||||
audio_url = blob_client.url
|
||||
|
||||
return JsonResponse({'code': 200, 'message': '上传成功', 'data': {'audio_url': audio_url}})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"上传文件时出错: {str(e)}")
|
||||
return JsonResponse({"code": 500, "message": str(e)})
|
||||
import re
|
||||
# 功能费用配置
|
||||
FEATURE_COSTS = {
|
||||
"text-to-video": {"base_cost": 10}, # 按字符收费
|
||||
"img-to-video": {"base_cost": 10}, # 固定收费
|
||||
"create-tiktok-video": {"base_cost": 20, "additional_cost_per_1000_chars": 10}, # 按字符收费
|
||||
"create-music-video": {"base_cost": 20}, # 固定收费
|
||||
"create-avatar-video": {"base_cost": 30, "additional_cost_per_1000_chars": 10}, # 按字符收费
|
||||
}
|
||||
|
||||
# 计算文本生成类功能的所需积分
|
||||
def calculate_points_for_text(text, base_cost, additional_cost_per_1000_chars=0):
|
||||
"""
|
||||
根据文本内容和定价策略计算所需积分。
|
||||
"""
|
||||
# 计算中文字符和非中文字符数量
|
||||
chinese_chars = re.findall(r'[\u4e00-\u9fff]', text)
|
||||
non_chinese_chars = re.findall(r'[^\u4e00-\u9fff]', text)
|
||||
|
||||
chinese_char_count = len(chinese_chars)
|
||||
non_chinese_char_count = len(non_chinese_chars)
|
||||
|
||||
# 中文字符算2个普通字符
|
||||
total_char_count = chinese_char_count * 2 + non_chinese_char_count
|
||||
|
||||
# 计算积分
|
||||
total_points = base_cost + (total_char_count // 1000) * additional_cost_per_1000_chars
|
||||
return total_points
|
||||
|
||||
# 扣费逻辑
|
||||
|
||||
def deduct_points(user, feature, text=None):
|
||||
"""
|
||||
根据不同功能类型扣费,返回 True 或 JsonResponse 提示积分不足。
|
||||
:param user: 用户对象
|
||||
:param feature: 功能类型 (如: text_to_video, img_to_video)
|
||||
:param text: 可选的文本参数
|
||||
:return: True 或者返回 JsonResponse
|
||||
"""
|
||||
# 检查功能类型是否在费用配置中
|
||||
if feature not in FEATURE_COSTS:
|
||||
return JsonResponse({'code': 400, 'message': f'无效的功能类型: {feature}', 'data': {}})
|
||||
|
||||
# 获取费用配置
|
||||
feature_config = FEATURE_COSTS[feature]
|
||||
base_cost = feature_config.get("base_cost", 0)
|
||||
additional_cost_per_1000_chars = feature_config.get("additional_cost_per_1000_chars", 0)
|
||||
|
||||
# 如果有文本传入,计算所需积分和字符数量
|
||||
char_count = 0
|
||||
if text:
|
||||
# 计算中文字符和非中文字符数量
|
||||
chinese_chars = re.findall(r'[\u4e00-\u9fff]', text)
|
||||
non_chinese_chars = re.findall(r'[^\u4e00-\u9fff]', text)
|
||||
|
||||
chinese_char_count = len(chinese_chars)
|
||||
non_chinese_char_count = len(non_chinese_chars)
|
||||
|
||||
# 中文字符算2个普通字符
|
||||
total_char_count = chinese_char_count * 2 + non_chinese_char_count
|
||||
char_count = total_char_count
|
||||
|
||||
# 计算积分
|
||||
required_points = calculate_points_for_text(text, base_cost, additional_cost_per_1000_chars)
|
||||
else:
|
||||
# 固定收费的功能
|
||||
required_points = base_cost
|
||||
|
||||
# 检查用户积分是否足够
|
||||
if user.points < required_points:
|
||||
return JsonResponse({'code': 402, 'message': '积分不足', 'data': {}})
|
||||
|
||||
# 扣费前记录用户积分
|
||||
previous_points_balance = user.points
|
||||
|
||||
# 扣除积分并保存
|
||||
user.points -= required_points
|
||||
user.save()
|
||||
|
||||
|
||||
# 记录消费明细到数据库
|
||||
TransactionHistory.objects.create(
|
||||
user=user,
|
||||
feature=feature,
|
||||
points_spent=required_points,
|
||||
char_count=char_count if text else None, # 记录字符数量
|
||||
previous_points_balance=previous_points_balance,
|
||||
new_points_balance=user.points,
|
||||
description=f'扣费 {required_points} 积分用于 {feature}',
|
||||
success=True
|
||||
)
|
||||
|
||||
# 返回成功扣费
|
||||
return True
|
||||
|
||||
|
||||
|
||||
71
WebAdmin/create_avatar_video.py
Executable file
71
WebAdmin/create_avatar_video.py
Executable file
@@ -0,0 +1,71 @@
|
||||
from .base import logger, synthesize_speech
|
||||
import requests
|
||||
|
||||
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
|
||||
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
|
||||
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
|
||||
|
||||
def create_avatar_video_payload(audio_url,generationPreset, media_type, text, ratio, voice_name, user_id, slug,disableCaptions, caption_preset, caption_position, selected_avatar, video_id):
|
||||
"""
|
||||
生成视频的函数(用于 create-avatar-video)
|
||||
"""
|
||||
try:
|
||||
video_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'key': VIDEO_API_KEY
|
||||
}
|
||||
|
||||
hasToGenerateVideos = media_type == "movingImage"
|
||||
|
||||
# 配置 webhook URL,包含 video_id
|
||||
webhook_url = f"{WEBHOOK_URL}?userid={user_id}&videoid={video_id}"
|
||||
|
||||
# 构建请求体
|
||||
payload = {
|
||||
"frameRate": 60,
|
||||
"resolution": "1080p",
|
||||
"frameDurationMultiplier": 18,
|
||||
"webhook": webhook_url,
|
||||
"creationParams": {
|
||||
"mediaType": media_type,
|
||||
"origin": "/create",
|
||||
"inputText": text,
|
||||
"flowType": "text-to-video",
|
||||
"slug": slug,
|
||||
"hasToGenerateVoice": True,
|
||||
"hasToTranscript": False,
|
||||
"hasToSearchMedia": True,
|
||||
"hasAvatar": True,
|
||||
"hasWebsiteRecorder": False,
|
||||
"hasTextSmallAtBottom": False,
|
||||
"captionPresetName": caption_preset,
|
||||
"captionPositionName": caption_position,
|
||||
"disableAudio": True,
|
||||
"useQualityTranscript":False,
|
||||
"ratio": ratio,
|
||||
"selectedAudio": "Bladerunner 2049",
|
||||
"selectedVoice": voice_name,
|
||||
"selectedAvatar": selected_avatar,
|
||||
"selectedAvatarType": "image/png",
|
||||
"sourceType": "contentScraping",
|
||||
"disableCaptions": disableCaptions,
|
||||
"selectedStoryStyle": {
|
||||
"value": "custom",
|
||||
"label": "Custom"
|
||||
},
|
||||
"hasToGenerateVideos": hasToGenerateVideos,
|
||||
"selectedRecording": audio_url,
|
||||
"selectedRecordingType": "audio",
|
||||
"generationPreset": generationPreset,
|
||||
"audioUrl": "https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3"
|
||||
}
|
||||
}
|
||||
logger.info(payload)
|
||||
response = requests.post(VIDEO_API_URL, headers=video_headers, json=payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {"code": 200, "data": response.json()}
|
||||
else:
|
||||
return {"code": response.status_code, "message": response.text}
|
||||
except Exception as e:
|
||||
return {"code": 500, "message": str(e)}
|
||||
71
WebAdmin/create_music_video.py
Executable file
71
WebAdmin/create_music_video.py
Executable file
@@ -0,0 +1,71 @@
|
||||
import os
|
||||
import requests
|
||||
from .base import logger
|
||||
|
||||
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
|
||||
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
|
||||
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
|
||||
|
||||
def create_music_video_payload(audio_url,generationPreset, media_type, text, ratio, voice_name, style, rate, user_id, slug, disableCaptions,caption_preset, caption_position, video_id):
|
||||
"""
|
||||
构建生成视频的请求体(用于 create-music-video)
|
||||
"""
|
||||
try:
|
||||
video_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'key': VIDEO_API_KEY
|
||||
}
|
||||
|
||||
# 配置 webhook URL,包含 video_id
|
||||
webhook_url = f"{WEBHOOK_URL}?userid={user_id}&videoid={video_id}"
|
||||
hasToGenerateVideos = media_type == "movingImage"
|
||||
|
||||
|
||||
# 构建请求体
|
||||
payload = {
|
||||
"frameRate": 60,
|
||||
"resolution": "1080p",
|
||||
"frameDurationMultiplier": 18,
|
||||
"webhook": webhook_url,
|
||||
"creationParams": {
|
||||
"mediaType": media_type,
|
||||
"hasSoundWave": False,
|
||||
"origin": "/create",
|
||||
"flowType": "file-to-video",
|
||||
"slug": slug,
|
||||
"hasToGenerateVoice": False,
|
||||
"hasToTranscript": False,
|
||||
"hasToSearchMedia": True,
|
||||
"hasAvatar": False,
|
||||
"hasWebsiteRecorder": False,
|
||||
"hasTextSmallAtBottom": False,
|
||||
"ratio": ratio,
|
||||
"selectedAudio": "Stomp",
|
||||
"disableCaptions": disableCaptions,
|
||||
"selectedVoice": voice_name,
|
||||
"captionPresetName": caption_preset,
|
||||
"captionPositionName": caption_position,
|
||||
"sourceType": "contentScraping",
|
||||
"selectedStoryStyle": {
|
||||
"value": "custom",
|
||||
"label": "Custom"
|
||||
},
|
||||
"generationPreset": generationPreset,
|
||||
"selectedRecording": audio_url,
|
||||
"selectedRecordingType": "audio",
|
||||
"useQualityTranscript": False,
|
||||
"hasEnhancedGeneration": False,
|
||||
"disableAudio": False,
|
||||
"hasToGenerateVideos": hasToGenerateVideos,
|
||||
"audioUrl": "https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3"
|
||||
}
|
||||
}
|
||||
logger.info(payload)
|
||||
response = requests.post(VIDEO_API_URL, headers=video_headers, json=payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {"code": 200, "data": response.json()}
|
||||
else:
|
||||
return {"code": response.status_code, "message": response.text}
|
||||
except Exception as e:
|
||||
return {"code": 500, "message": str(e)}
|
||||
332
WebAdmin/create_text_img_video.py
Executable file
332
WebAdmin/create_text_img_video.py
Executable file
@@ -0,0 +1,332 @@
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from django.http import JsonResponse
|
||||
from .models import VideoGeneration
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from .base import logger,deduct_points
|
||||
def generate_video_id(user_id):
|
||||
"""
|
||||
根据用户ID和当前时间戳生成唯一的视频ID
|
||||
"""
|
||||
timestamp = int(time.time())
|
||||
video_id = f"{timestamp}_{user_id}"
|
||||
print(f"生成的视频ID: {video_id}")
|
||||
return video_id
|
||||
|
||||
def get_dimensions(model, width=None, height=None):
|
||||
"""
|
||||
根据模型返回相应的宽高分辨率:
|
||||
gen3: 最大1280x768
|
||||
gen2: 可传入前端指定的宽高,默认最大1920x1080
|
||||
"""
|
||||
if model == 'gen3':
|
||||
return 1280, 768
|
||||
elif model == 'gen2':
|
||||
# 如果前端提供了 width 和 height,则使用传入的值,否则使用默认值
|
||||
if width and height:
|
||||
return width, height
|
||||
return 1920, 1080
|
||||
else:
|
||||
raise ValueError(f"不支持的模型: {model}")
|
||||
|
||||
def get_headers():
|
||||
"""
|
||||
返回统一的请求头
|
||||
"""
|
||||
return {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
"Authorization": "12e76710fad2047db8c0cc6b25987e2a2" # 替换为你的真实授权密钥
|
||||
}
|
||||
|
||||
@csrf_exempt
|
||||
def text_to_video(request):
|
||||
"""
|
||||
文本生成视频接口
|
||||
"""
|
||||
if request.method == "POST":
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
user = request.user
|
||||
data = json.loads(request.body)
|
||||
|
||||
# 获取参数
|
||||
text_prompt = data.get('text_prompt')
|
||||
style = data.get('style', '') # 选择的风格,默认是 'general'
|
||||
model = data.get('model', 'gen3') # 选择模型,默认是 'gen3'
|
||||
time_duration = int(data.get('time', 5)) # 视频时长
|
||||
motion = int(data.get('motion', 5)) # 视频丰富度参数
|
||||
width = data.get('width') # 由前端传入的宽度(仅限gen2)
|
||||
height = data.get('height') # 由前端传入的高度(仅限gen2)
|
||||
|
||||
print(f"生成模型: {model}, 时长: {time_duration}, 文本: {text_prompt}, 宽高: {width}x{height}, 画面丰富度: {motion}")
|
||||
|
||||
result = deduct_points(user, "text-to-video")
|
||||
if result is not True:
|
||||
return result # 返回积分不足的响应
|
||||
|
||||
# 验证模型和时长限制
|
||||
if model == 'gen3' and time_duration not in [5, 10]:
|
||||
return JsonResponse({'code': 400, 'message': 'gen3 模型只支持 5s 或 10s 的时长'})
|
||||
if model == 'gen2' and time_duration != 4:
|
||||
return JsonResponse({'code': 400, 'message': 'gen2 模型只支持 4s 的时长'})
|
||||
|
||||
# 生成视频ID
|
||||
video_id = generate_video_id(user.id)
|
||||
|
||||
# 根据模型获取宽高
|
||||
width, height = get_dimensions(model, width, height)
|
||||
print(f"生成的视频分辨率: {width}x{height}")
|
||||
|
||||
# 准备POST请求数据
|
||||
payload = {
|
||||
"text_prompt": f"{text_prompt}. Style: {style}",
|
||||
"model": model,
|
||||
"width": width,
|
||||
"height": height,
|
||||
"motion": motion, # 视频画面丰富度
|
||||
"seed": 0,
|
||||
"upscale": False,
|
||||
"interpolate": False,
|
||||
"callback_url": "https://www.typeframes.ai/api/callback/",
|
||||
"time": time_duration # 视频时长
|
||||
}
|
||||
print(f'text_to_video 请求体: {payload}')
|
||||
|
||||
# 发送请求到API
|
||||
response = requests.post('https://api.aivideoapi.com/runway/generate/text', json=payload, headers=get_headers())
|
||||
print(f"API响应状态码: {response.status_code}")
|
||||
print(f"API响应内容: {response.text}")
|
||||
|
||||
# 解析响应数据
|
||||
if response.status_code == 200:
|
||||
res_data = response.json()
|
||||
uuid = res_data.get('uuid')
|
||||
|
||||
# 保存生成记录到数据库
|
||||
video_generation = VideoGeneration.objects.create(
|
||||
video_id=video_id,
|
||||
user=user,
|
||||
text=text_prompt,
|
||||
status='in_progress',
|
||||
pid=uuid,
|
||||
media_type=f'{model}_text_to_video',
|
||||
slug=f'text-to-video',
|
||||
ratio=f"{width}:{height}"
|
||||
)
|
||||
video_generation.save()
|
||||
|
||||
# 返回成功响应
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '请求成功,视频正在生成中',
|
||||
'data': {
|
||||
'video_id': video_generation.video_id,
|
||||
'status': video_generation.status,
|
||||
'pid': uuid
|
||||
}
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
|
||||
|
||||
except Exception as e:
|
||||
print(f"发生错误: {str(e)}")
|
||||
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
|
||||
|
||||
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
|
||||
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def image_to_video(request):
|
||||
"""
|
||||
图片生成视频接口,图片宽高自适应
|
||||
"""
|
||||
if request.method == "POST":
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
user = request.user
|
||||
data = json.loads(request.body)
|
||||
|
||||
# 获取参数
|
||||
text_prompt = data.get('text_prompt')
|
||||
image_url = data.get('image_url')
|
||||
model = data.get('model', 'gen3') # 选择模型,默认是 'gen3'
|
||||
time_duration = int(data.get('time', 5)) # 视频时长
|
||||
motion = int(data.get('motion', 5)) # 视频丰富度
|
||||
|
||||
print(f"生成模型: {model}, 图片地址: {image_url}, 时长: {time_duration}, 画面丰富度: {motion}")
|
||||
|
||||
result = deduct_points(user, "img-to-video")
|
||||
if result is not True:
|
||||
return result # 返回积分不足的响应
|
||||
|
||||
# 验证模型和时长限制
|
||||
if model == 'gen3' and time_duration not in [5, 10]:
|
||||
return JsonResponse({'code': 400, 'message': 'gen3 模型只支持 5s 或 10s 的时长'})
|
||||
if model == 'gen2' and time_duration != 4:
|
||||
return JsonResponse({'code': 400, 'message': 'gen2 模型只支持 4s 的时长'})
|
||||
|
||||
# 生成视频ID
|
||||
video_id = generate_video_id(user.id)
|
||||
|
||||
# 准备POST请求数据,宽高不需要传入,图片自适应
|
||||
payload = {
|
||||
"text_prompt": text_prompt,
|
||||
"model": model,
|
||||
"img_prompt": image_url,
|
||||
"image_as_end_frame": False, # 图片不作为最后一帧
|
||||
"motion": motion, # 视频丰富度
|
||||
"seed": 0,
|
||||
"upscale": False,
|
||||
"interpolate": False,
|
||||
"callback_url": "https://www.typeframes.ai/api/callback/",
|
||||
"time": time_duration # 视频时长
|
||||
}
|
||||
print(f'img_to_video 请求体: {payload}')
|
||||
|
||||
# 请求头
|
||||
headers = get_headers()
|
||||
|
||||
# 发送请求到API
|
||||
response = requests.post('https://api.aivideoapi.com/runway/generate/imageDescription', json=payload, headers=headers)
|
||||
print(f"API响应状态码: {response.status_code}")
|
||||
print(f"API响应内容: {response.text}")
|
||||
|
||||
# 解析响应数据
|
||||
if response.status_code == 200:
|
||||
res_data = response.json()
|
||||
uuid = res_data.get('uuid')
|
||||
|
||||
# 保存生成记录到数据库
|
||||
video_generation = VideoGeneration.objects.create(
|
||||
video_id=video_id,
|
||||
user=user,
|
||||
text=text_prompt,
|
||||
status='in_progress',
|
||||
pid=uuid,
|
||||
media_type=f'{model}_img_to_video',
|
||||
slug=f'img-to-video',
|
||||
ratio="auto" # 图片自适应宽高
|
||||
)
|
||||
video_generation.save()
|
||||
|
||||
# 返回成功响应
|
||||
return JsonResponse({
|
||||
'code': 200,
|
||||
'message': '请求成功,视频正在生成中',
|
||||
'data': {
|
||||
'video_id': video_generation.video_id,
|
||||
'status': video_generation.status,
|
||||
'pid': uuid
|
||||
}
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
|
||||
|
||||
except Exception as e:
|
||||
print(f"发生错误: {str(e)}")
|
||||
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
|
||||
|
||||
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
|
||||
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def extend_video(request):
|
||||
"""
|
||||
延长视频的函数,发送请求到 /runway/extend
|
||||
"""
|
||||
if request.method == "POST":
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'code': 401, 'message': '用户未登录'})
|
||||
user = request.user
|
||||
data = json.loads(request.body)
|
||||
|
||||
# 获取参数
|
||||
pid = data.get('pid')
|
||||
motion = int(data.get('motion', 5)) # 视频丰富度
|
||||
|
||||
# 获取当前视频生成记录
|
||||
video_generation = VideoGeneration.objects.get(pid=pid)
|
||||
|
||||
# 通过 media_type 判断模型是 gen3 还是 gen2
|
||||
if 'gen3' in video_generation.media_type:
|
||||
model = 'gen3'
|
||||
# 验证 gen3 的延长逻辑
|
||||
if video_generation.extension_count >= 1 or video_generation.time_duration >= 10:
|
||||
return JsonResponse({'code': 400, 'message': 'gen3 最大时长为 10s,无法继续延长'})
|
||||
time_extension = 5 # 每次延长 5 秒
|
||||
|
||||
elif 'gen2' in video_generation.media_type:
|
||||
model = 'gen2'
|
||||
# 验证 gen2 的延长逻辑
|
||||
if video_generation.extension_count >= 2 or video_generation.time_duration >= 12:
|
||||
return JsonResponse({'code': 400, 'message': 'gen2 最大时长为 12s,无法继续延长'})
|
||||
time_extension = 4 # 每次延长 4 秒
|
||||
|
||||
else:
|
||||
return JsonResponse({'code': 400, 'message': '未知的模型类型'})
|
||||
|
||||
result = deduct_points(user, video_generation.slug)
|
||||
if result is not True:
|
||||
return result # 返回积分不足的响应
|
||||
|
||||
# 生成新的视频ID
|
||||
new_video_id = generate_video_id(user.id)
|
||||
|
||||
# 默认请求体
|
||||
payload = {
|
||||
"uuid": pid, # 视频的 UUID
|
||||
"motion": motion, # 视频丰富度
|
||||
"seed": 0, # 默认 seed 为 0
|
||||
"upscale": False, # 默认开启 upscale
|
||||
"interpolate": False, # 默认开启 interpolate
|
||||
"callback_url": "https://www.typeframes.ai/api/callback/"
|
||||
}
|
||||
print(f"extend_video 请求体: {payload}")
|
||||
|
||||
headers = get_headers()
|
||||
|
||||
# 发送 POST 请求
|
||||
response = requests.post('https://api.aivideoapi.com/runway/extend', json=payload, headers=headers)
|
||||
print(f"API响应状态码: {response.status_code}")
|
||||
print(f"API响应内容: {response.text}")
|
||||
|
||||
# 解析响应内容
|
||||
if response.status_code == 200:
|
||||
res_data = response.json()
|
||||
new_pid = res_data.get('uuid')
|
||||
|
||||
# 创建新的延长视频记录,复制上一条视频的参数
|
||||
new_video_generation = VideoGeneration.objects.create(
|
||||
video_id=new_video_id,
|
||||
user=user,
|
||||
text=video_generation.text,
|
||||
voice_name=video_generation.voice_name,
|
||||
style=video_generation.style,
|
||||
rate=video_generation.rate,
|
||||
media_type=video_generation.media_type,
|
||||
ratio=video_generation.ratio,
|
||||
status='in_progress',
|
||||
pid=new_pid,
|
||||
time_duration=video_generation.time_duration + time_extension, # 增加对应模型的时长
|
||||
extension_count=video_generation.extension_count + 1, # 增加延长次数
|
||||
slug=video_generation.slug # slug 使用中划线
|
||||
)
|
||||
print(f"延长视频成功: {res_data}")
|
||||
|
||||
return JsonResponse({'code': 200, 'message': '视频延长请求成功', 'data': {'new_pid': new_pid}})
|
||||
|
||||
else:
|
||||
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
|
||||
|
||||
except Exception as e:
|
||||
print(f"发生错误: {str(e)}")
|
||||
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
|
||||
|
||||
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
|
||||
142
WebAdmin/create_text_video.py
Executable file
142
WebAdmin/create_text_video.py
Executable file
@@ -0,0 +1,142 @@
|
||||
import requests
|
||||
from django.http import JsonResponse
|
||||
from .models import VideoGeneration
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
def text_to_video(request):
|
||||
"""
|
||||
接口:根据文本描述生成视频
|
||||
"""
|
||||
if request.method == "POST":
|
||||
try:
|
||||
user = request.user
|
||||
data = request.POST
|
||||
|
||||
# 生成视频ID
|
||||
video_id = generate_video_id(user.id)
|
||||
|
||||
# 获取参数
|
||||
text_prompt = data.get('text_prompt')
|
||||
model = data.get('model', 'gen3')
|
||||
width = int(data.get('width', 640))
|
||||
height = int(data.get('height', 480))
|
||||
motion = int(data.get('motion', 5))
|
||||
seed = int(data.get('seed', 0))
|
||||
upscale = data.get('upscale', 'false') == 'true'
|
||||
interpolate = data.get('interpolate', 'false') == 'true'
|
||||
callback_url = data.get('callback_url', '')
|
||||
time_duration = int(data.get('time', 5))
|
||||
|
||||
# 准备POST请求数据
|
||||
payload = {
|
||||
"text_prompt": text_prompt,
|
||||
"model": model,
|
||||
"width": width,
|
||||
"height": height,
|
||||
"motion": motion,
|
||||
"seed": seed,
|
||||
"upscale": upscale,
|
||||
"interpolate": interpolate,
|
||||
"callback_url": callback_url,
|
||||
"time": time_duration
|
||||
}
|
||||
|
||||
# 发送请求到API
|
||||
response = requests.post('https://api.aivideoapi.com/runway/generate/text', json=payload)
|
||||
|
||||
# 解析响应数据
|
||||
if response.status_code == 200:
|
||||
res_data = response.json()
|
||||
uuid = res_data.get('uuid')
|
||||
|
||||
# 保存生成记录到数据库
|
||||
video_generation = VideoGeneration.objects.create(
|
||||
video_id=video_id,
|
||||
user=user,
|
||||
text=text_prompt,
|
||||
status='in_progress',
|
||||
pid=uuid,
|
||||
ratio=f"{width}:{height}"
|
||||
)
|
||||
video_generation.save()
|
||||
|
||||
return JsonResponse({'code': 200, 'message': '请求成功', 'data': {'pid': uuid}})
|
||||
|
||||
else:
|
||||
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
|
||||
|
||||
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
def image_to_video(request):
|
||||
"""
|
||||
接口:根据图片生成视频
|
||||
"""
|
||||
if request.method == "POST":
|
||||
try:
|
||||
user = request.user
|
||||
data = request.POST
|
||||
|
||||
# 生成视频ID
|
||||
video_id = generate_video_id(user.id)
|
||||
|
||||
# 获取参数
|
||||
text_prompt = data.get('text_prompt')
|
||||
model = data.get('model', 'gen3')
|
||||
img_prompt = data.get('img_prompt')
|
||||
image_as_end_frame = data.get('image_as_end_frame', 'false') == 'true'
|
||||
motion = int(data.get('motion', 5))
|
||||
seed = int(data.get('seed', 0))
|
||||
upscale = data.get('upscale', 'false') == 'true'
|
||||
interpolate = data.get('interpolate', 'false') == 'true'
|
||||
callback_url = data.get('callback_url', '')
|
||||
time_duration = int(data.get('time', 5))
|
||||
|
||||
# 准备POST请求数据
|
||||
payload = {
|
||||
"text_prompt": text_prompt,
|
||||
"model": model,
|
||||
"img_prompt": img_prompt,
|
||||
"image_as_end_frame": image_as_end_frame,
|
||||
"motion": motion,
|
||||
"seed": seed,
|
||||
"upscale": upscale,
|
||||
"interpolate": interpolate,
|
||||
"callback_url": callback_url,
|
||||
"time": time_duration
|
||||
}
|
||||
|
||||
# 发送请求到API
|
||||
response = requests.post('https://api.aivideoapi.com/runway/generate/imageDescription', json=payload)
|
||||
|
||||
# 解析响应数据
|
||||
if response.status_code == 200:
|
||||
res_data = response.json()
|
||||
uuid = res_data.get('uuid')
|
||||
|
||||
# 保存生成记录到数据库
|
||||
video_generation = VideoGeneration.objects.create(
|
||||
video_id=video_id,
|
||||
user=user,
|
||||
text=text_prompt,
|
||||
status='in_progress',
|
||||
pid=uuid,
|
||||
ratio="auto"
|
||||
)
|
||||
video_generation.save()
|
||||
|
||||
return JsonResponse({'code': 200, 'message': '请求成功', 'data': {'pid': uuid}})
|
||||
|
||||
else:
|
||||
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
|
||||
|
||||
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
|
||||
70
WebAdmin/create_tiktok_video.py
Executable file
70
WebAdmin/create_tiktok_video.py
Executable file
@@ -0,0 +1,70 @@
|
||||
from .base import logger, synthesize_speech
|
||||
import requests
|
||||
|
||||
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
|
||||
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
|
||||
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
|
||||
|
||||
def create_video(audio_url,generationPreset, media_type, text, ratio, voice_name, user_id, slug,disableCaptions, caption_preset, caption_position, video_id):
|
||||
"""
|
||||
生成视频的函数(用于 create-tiktok-video)
|
||||
"""
|
||||
try:
|
||||
video_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'key': VIDEO_API_KEY
|
||||
}
|
||||
|
||||
# 判断是否需要生成视频
|
||||
hasToGenerateVideos = media_type == "movingImage"
|
||||
|
||||
# 配置 webhook URL,包含 video_id
|
||||
webhook_url = f"{WEBHOOK_URL}?userid={user_id}&videoid={video_id}"
|
||||
|
||||
# 构建请求体
|
||||
payload = {
|
||||
"frameRate": 60,
|
||||
"resolution": "1080p",
|
||||
"frameDurationMultiplier": 5,
|
||||
"webhook": webhook_url,
|
||||
"creationParams": {
|
||||
"mediaType": media_type,
|
||||
"origin": "/create",
|
||||
"inputText": text,
|
||||
"flowType": "text-to-video",
|
||||
"slug": slug,
|
||||
"disableCaptions": disableCaptions,
|
||||
"hasToGenerateVoice": False,
|
||||
"hasToTranscript": False,
|
||||
"hasToSearchMedia": True,
|
||||
"useQualityTranscript":False,
|
||||
"hasAvatar": False,
|
||||
"hasWebsiteRecorder": False,
|
||||
"hasTextSmallAtBottom": False,
|
||||
"captionPresetName": caption_preset,
|
||||
"captionPositionName": caption_position,
|
||||
"disableAudio": True,
|
||||
"ratio": ratio,
|
||||
"selectedAudio": "Bladerunner 2049",
|
||||
"selectedVoice": voice_name,
|
||||
"sourceType": "contentScraping",
|
||||
"selectedStoryStyle": {
|
||||
"value": "custom",
|
||||
"label": "Custom"
|
||||
},
|
||||
"hasToGenerateVideos": hasToGenerateVideos,
|
||||
"selectedRecording": audio_url,
|
||||
"selectedRecordingType": "audio",
|
||||
"generationPreset": generationPreset,
|
||||
"audioUrl": "https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3"
|
||||
}
|
||||
}
|
||||
logger.info(payload)
|
||||
response = requests.post(VIDEO_API_URL, headers=video_headers, json=payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
return {"code": 200, "data": response.json()}
|
||||
else:
|
||||
return {"code": response.status_code, "message": response.text}
|
||||
except Exception as e:
|
||||
return {"code": 500, "message": str(e)}
|
||||
595
WebAdmin/data.json
Executable file
595
WebAdmin/data.json
Executable file
@@ -0,0 +1,595 @@
|
||||
[
|
||||
{
|
||||
"国家": "中国",
|
||||
"语言参数": "zh-CN",
|
||||
"语言": "中文(普通话,简体)",
|
||||
"讲话人": [
|
||||
{
|
||||
"讲话人中文名": "晓晓",
|
||||
"讲话人英文参数": "zh-CN-XiaoxiaoNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云希",
|
||||
"讲话人英文参数": "zh-CN-YunxiNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云健",
|
||||
"讲话人英文参数": "zh-CN-YunjianNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓艺",
|
||||
"讲话人英文参数": "zh-CN-XiaoyiNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云阳",
|
||||
"讲话人英文参数": "zh-CN-YunyangNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓晨",
|
||||
"讲话人英文参数": "zh-CN-XiaochenNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓涵",
|
||||
"讲话人英文参数": "zh-CN-XiaohanNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓萌",
|
||||
"讲话人英文参数": "zh-CN-XiaomengNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓陌",
|
||||
"讲话人英文参数": "zh-CN-XiaomoNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓秋",
|
||||
"讲话人英文参数": "zh-CN-XiaoqiuNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓蕊",
|
||||
"讲话人英文参数": "zh-CN-XiaoruiNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓双",
|
||||
"讲话人英文参数": "zh-CN-XiaoshuangNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓妍",
|
||||
"讲话人英文参数": "zh-CN-XiaoyanNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓优",
|
||||
"讲话人英文参数": "zh-CN-XiaoyouNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓真",
|
||||
"讲话人英文参数": "zh-CN-XiaozhenNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云峰",
|
||||
"讲话人英文参数": "zh-CN-YunfengNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云浩",
|
||||
"讲话人英文参数": "zh-CN-YunhaoNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云霞",
|
||||
"讲话人英文参数": "zh-CN-YunxiaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云烨",
|
||||
"讲话人英文参数": "zh-CN-YunyeNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云泽",
|
||||
"讲话人英文参数": "zh-CN-YunzeNeural",
|
||||
"风格列表": [],
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"国家": "香港特别行政区",
|
||||
"语言参数": "zh-HK",
|
||||
"语言": "中文(粤语,繁体)",
|
||||
"讲话人": [
|
||||
{
|
||||
"讲话人中文名": "晓文",
|
||||
"讲话人英文参数": "zh-HK-HiuMaanNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云龙",
|
||||
"讲话人英文参数": "zh-HK-WanLungNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓佳",
|
||||
"讲话人英文参数": "zh-HK-HiuGaaiNeural",
|
||||
"风格列表": [],
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"国家": "台湾",
|
||||
"语言参数": "zh-TW",
|
||||
"语言": "中文(台湾普通话,繁体)",
|
||||
"讲话人": [
|
||||
{
|
||||
"讲话人中文名": "晓晨",
|
||||
"讲话人英文参数": "zh-TW-HsiaoChenNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "云哲",
|
||||
"讲话人英文参数": "zh-TW-YunJheNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "晓语",
|
||||
"讲话人英文参数": "zh-TW-HsiaoYuNeural",
|
||||
"风格列表": [],
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"国家": "美国",
|
||||
"语言参数": "en-US",
|
||||
"语言": "英语(美国)",
|
||||
"讲话人": [
|
||||
{
|
||||
"讲话人中文名": "Ava",
|
||||
"讲话人英文参数": "en-US-AvaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Andrew",
|
||||
"讲话人英文参数": "en-US-AndrewNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Emma",
|
||||
"讲话人英文参数": "en-US-EmmaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Brian",
|
||||
"讲话人英文参数": "en-US-BrianNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Jenny",
|
||||
"讲话人英文参数": "en-US-JennyNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Guy",
|
||||
"讲话人英文参数": "en-US-GuyNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Aria",
|
||||
"讲话人英文参数": "en-US-AriaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Davis",
|
||||
"讲话人英文参数": "en-US-DavisNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Jane",
|
||||
"讲话人英文参数": "en-US-JaneNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Jason",
|
||||
"讲话人英文参数": "en-US-JasonNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Sara",
|
||||
"讲话人英文参数": "en-US-SaraNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Tony",
|
||||
"讲话人英文参数": "en-US-TonyNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Nancy",
|
||||
"讲话人英文参数": "en-US-NancyNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Amber",
|
||||
"讲话人英文参数": "en-US-AmberNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Ana",
|
||||
"讲话人英文参数": "en-US-AnaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Ashley",
|
||||
"讲话人英文参数": "en-US-AshleyNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Brandon",
|
||||
"讲话人英文参数": "en-US-BrandonNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Christopher",
|
||||
"讲话人英文参数": "en-US-ChristopherNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Cora",
|
||||
"讲话人英文参数": "en-US-CoraNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Elizabeth",
|
||||
"讲话人英文参数": "en-US-ElizabethNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Eric",
|
||||
"讲话人英文参数": "en-US-EricNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Jacob",
|
||||
"讲话人英文参数": "en-US-JacobNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Jenny",
|
||||
"讲话人英文参数": "en-US-JennyMultilingualNeural3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Michelle",
|
||||
"讲话人英文参数": "en-US-MichelleNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Monica",
|
||||
"讲话人英文参数": "en-US-MonicaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Roger",
|
||||
"讲话人英文参数": "en-US-RogerNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Ryan",
|
||||
"讲话人英文参数": "en-US-RyanMultilingualNeural3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Steffan",
|
||||
"讲话人英文参数": "en-US-SteffanNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "AIGenerate1",
|
||||
"讲话人英文参数": "en-US-AIGenerate1Neural1",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "AIGenerate2",
|
||||
"讲话人英文参数": "en-US-AIGenerate2Neural1",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Andrew",
|
||||
"讲话人英文参数": "en-US-AndrewMultilingualNeural3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Ava",
|
||||
"讲话人英文参数": "en-US-AvaMultilingualNeural3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Blue",
|
||||
"讲话人英文参数": "en-US-BlueNeural1",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Kai",
|
||||
"讲话人英文参数": "en-US-KaiNeural1",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Luna",
|
||||
"讲话人英文参数": "en-US-LunaNeural1",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Brian",
|
||||
"讲话人英文参数": "en-US-BrianMultilingualNeural3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Emma",
|
||||
"讲话人英文参数": "en-US-EmmaMultilingualNeural3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Alloy",
|
||||
"讲话人英文参数": "en-US-AlloyMultilingualNeural4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Echo",
|
||||
"讲话人英文参数": "en-US-EchoMultilingualNeural4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Fable",
|
||||
"讲话人英文参数": "en-US-FableMultilingualNeural4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Onyx",
|
||||
"讲话人英文参数": "en-US-OnyxMultilingualNeural4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Nova",
|
||||
"讲话人英文参数": "en-US-NovaMultilingualNeural4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Shimmer",
|
||||
"讲话人英文参数": "en-US-ShimmerMultilingualNeural4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Alloy",
|
||||
"讲话人英文参数": "en-US-AlloyMultilingualNeuralHD4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Echo",
|
||||
"讲话人英文参数": "en-US-EchoMultilingualNeuralHD4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Fable",
|
||||
"讲话人英文参数": "en-US-FableMultilingualNeuralHD4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Onyx",
|
||||
"讲话人英文参数": "en-US-OnyxMultilingualNeuralHD4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Nova",
|
||||
"讲话人英文参数": "en-US-NovaMultilingualNeuralHD4",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Shimmer",
|
||||
"讲话人英文参数": "en-US-ShimmerMultilingualNeuralHD4",
|
||||
"风格列表": [],
|
||||
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"国家": "西班牙",
|
||||
"语言参数": "es-ES",
|
||||
"语言": "西班牙语(西班牙)",
|
||||
"讲话人": [
|
||||
{
|
||||
"讲话人中文名": "Elvira",
|
||||
"讲话人英文参数": "es-ES-ElviraNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Alvaro",
|
||||
"讲话人英文参数": "es-ES-AlvaroNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Abril",
|
||||
"讲话人英文参数": "es-ES-AbrilNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Arnau",
|
||||
"讲话人英文参数": "es-ES-ArnauNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Dario",
|
||||
"讲话人英文参数": "es-ES-DarioNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Elias",
|
||||
"讲话人英文参数": "es-ES-EliasNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Estrella",
|
||||
"讲话人英文参数": "es-ES-EstrellaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Irene",
|
||||
"讲话人英文参数": "es-ES-IreneNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Laia",
|
||||
"讲话人英文参数": "es-ES-LaiaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Lia",
|
||||
"讲话人英文参数": "es-ES-LiaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Nil",
|
||||
"讲话人英文参数": "es-ES-NilNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Saul",
|
||||
"讲话人英文参数": "es-ES-SaulNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Teo",
|
||||
"讲话人英文参数": "es-ES-TeoNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Triana",
|
||||
"讲话人英文参数": "es-ES-TrianaNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Vera",
|
||||
"讲话人英文参数": "es-ES-VeraNeural",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Ximena",
|
||||
"讲话人英文参数": "es-ES-XimenaNeural1",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Arabella",
|
||||
"讲话人英文参数": "es-ES-ArabellaMultilingualNeural1,3",
|
||||
"风格列表": [],
|
||||
|
||||
},
|
||||
{
|
||||
"讲话人中文名": "Isidora",
|
||||
"讲话人英文参数": "es-ES-IsidoraMultilingualNeural1,3",
|
||||
"风格列表": [],
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
0
WebAdmin/data.json.py
Executable file
0
WebAdmin/data.json.py
Executable file
35
WebAdmin/data_style.json
Executable file
35
WebAdmin/data_style.json
Executable file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"de-DE-ConradNeural1": ["cheerful"],
|
||||
"en-GB-SoniaNeural": ["cheerful", "sad"],
|
||||
"en-US-AriaNeural": ["angry", "chat", "cheerful", "customerservice", "empathetic", "excited", "friendly", "hopeful", "narration-professional", "newscast-casual", "newscast-formal", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-DavisNeural": ["angry", "chat", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-GuyNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "newscast", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-JaneNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-JasonNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-JennyNeural": ["angry", "assistant", "chat", "cheerful", "customerservice", "excited", "friendly", "hopeful", "newscast", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-NancyNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-SaraNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"en-US-TonyNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
|
||||
"es-MX-JorgeNeural": ["chat", "cheerful"],
|
||||
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
||||
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
||||
"it-IT-IsabellaNeural": ["chat", "cheerful"],
|
||||
"ja-JP-NanamiNeural": ["chat", "cheerful", "customerservice"],
|
||||
"pt-BR-FranciscaNeural": ["calm"],
|
||||
"zh-CN-XiaohanNeural": ["affectionate", "angry", "calm", "cheerful", "disgruntled", "embarrassed", "fearful", "gentle", "sad", "serious"],
|
||||
"zh-CN-XiaomengNeural": ["chat"],
|
||||
"zh-CN-XiaomoNeural": ["affectionate", "angry", "calm", "cheerful", "depressed", "disgruntled", "embarrassed", "envious", "fearful", "gentle", "sad", "serious"],
|
||||
"zh-CN-XiaoruiNeural": ["angry", "calm", "fearful", "sad"],
|
||||
"zh-CN-XiaoshuangNeural": ["chat"],
|
||||
"zh-CN-XiaoxiaoNeural": ["affectionate", "angry", "assistant", "calm", "chat", "chat-casual", "cheerful", "customerservice", "disgruntled", "fearful", "friendly", "gentle", "lyrical", "newscast", "poetry-reading", "sad", "serious", "sorry", "whisper"],
|
||||
"zh-CN-XiaoyiNeural": ["affectionate", "angry", "cheerful", "disgruntled", "embarrassed", "fearful", "gentle", "sad", "serious"],
|
||||
"zh-CN-XiaozhenNeural": ["angry", "cheerful", "disgruntled", "fearful", "sad", "serious"],
|
||||
"zh-CN-YunfengNeural": ["angry", "cheerful", "depressed", "disgruntled", "fearful", "sad", "serious"],
|
||||
"zh-CN-YunhaoNeural2": ["advertisement-upbeat"],
|
||||
"zh-CN-YunjianNeural3,4": ["angry", "cheerful", "depressed", "disgruntled", "documentary-narration", "narration-relaxed", "sad", "serious", "sports-commentary", "sports-commentary-excited"],
|
||||
"zh-CN-YunxiaNeural": ["angry", "calm", "cheerful", "fearful", "sad"],
|
||||
"zh-CN-YunxiNeural": ["angry", "assistant", "chat", "cheerful", "depressed", "disgruntled", "embarrassed", "fearful", "narration-relaxed", "newscast", "sad", "serious"],
|
||||
"zh-CN-YunyangNeural": ["customerservice", "narration-professional", "newscast-casual"],
|
||||
"zh-CN-YunyeNeural": ["angry", "calm", "cheerful", "disgruntled", "embarrassed", "fearful", "sad", "serious"],
|
||||
"zh-CN-YunzeNeural": ["angry", "calm", "cheerful", "depressed", "disgruntled", "documentary-narration", "fearful", "sad", "serious"]
|
||||
}
|
||||
76
WebAdmin/mail.py
Executable file
76
WebAdmin/mail.py
Executable file
@@ -0,0 +1,76 @@
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||
from tencentcloud.ses.v20201002 import ses_client, models
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
from .models import EmailVerificationCode
|
||||
|
||||
def generate_verification_code(length=6):
|
||||
return get_random_string(length=length, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
|
||||
|
||||
def send_email_with_template(secret_id, secret_key, from_email, to_email, subject, template_id, code):
|
||||
try:
|
||||
# 实例化一个认证对象
|
||||
cred = credential.Credential(secret_id, secret_key)
|
||||
httpProfile = HttpProfile()
|
||||
httpProfile.endpoint = "ses.tencentcloudapi.com"
|
||||
clientProfile = ClientProfile()
|
||||
clientProfile.httpProfile = httpProfile
|
||||
client = ses_client.SesClient(cred, "ap-hongkong", clientProfile)
|
||||
|
||||
# 实例化一个请求对象
|
||||
req = models.SendEmailRequest()
|
||||
params = {
|
||||
"FromEmailAddress":f'typeframes <{from_email}>',
|
||||
"Destination": [to_email],
|
||||
"Subject": subject,
|
||||
|
||||
"ReplyToAddresses": from_email,
|
||||
"Template": {
|
||||
"TemplateID": template_id,
|
||||
"TemplateData": json.dumps({"name": to_email, "code": code})
|
||||
}
|
||||
}
|
||||
|
||||
req.from_json_string(json.dumps(params))
|
||||
|
||||
# 发送请求并返回响应
|
||||
resp = client.SendEmail(req)
|
||||
return resp.to_json_string()
|
||||
|
||||
except TencentCloudSDKException as err:
|
||||
return str(err)
|
||||
|
||||
def send_verification_email(email):
|
||||
code = generate_verification_code()
|
||||
subject = '您的验证码'
|
||||
template_id = 125447 # 替换为实际的模板 ID
|
||||
from_email = settings.EMAIL_HOST_USER
|
||||
|
||||
response = send_email_with_template(settings.SECRET_ID, settings.MAIL_SECRET_KEY, from_email, email, subject, template_id, code)
|
||||
response_json = json.loads(response)
|
||||
if "Error" in response_json.get("Response", {}):
|
||||
return {'code': 400, 'message': f'发送邮件失败: {response_json["Response"]["Error"]["Message"]}'}
|
||||
|
||||
EmailVerificationCode.objects.create(email=email, code=code, created_at=datetime.now())
|
||||
return {'code': 200, 'message': '验证码发送成功'}
|
||||
|
||||
def verify_code(email, code):
|
||||
try:
|
||||
email_code = EmailVerificationCode.objects.filter(email=email, code=code, is_used=False).latest('created_at')
|
||||
if email_code is None:
|
||||
return {'code': 400, 'message': '请先获取验证码'}
|
||||
|
||||
if email_code.created_at < datetime.now() - timedelta(minutes=5):
|
||||
return {'code': 400, 'message': '验证码已过期'}
|
||||
|
||||
email_code.is_used = True
|
||||
email_code.save()
|
||||
return {'code': 200, 'message': '验证码验证成功'}
|
||||
except EmailVerificationCode.DoesNotExist:
|
||||
return {'code': 400, 'message': '验证码无效或已过期'}
|
||||
57
WebAdmin/mail_demo.py
Executable file
57
WebAdmin/mail_demo.py
Executable file
@@ -0,0 +1,57 @@
|
||||
import json
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||
from tencentcloud.ses.v20201002 import ses_client, models
|
||||
|
||||
SECRET_ID = "AKIDjNrBSnBXnAQXg6ythOADLJ2nOuLpFaoo"
|
||||
MAIL_SECRET_KEY = "ENPZnwADD623HUm15EITdvDhAa8nQxLk"
|
||||
EMAIL_HOST_USER = 'admin@typeframes.cc'
|
||||
TO_EMAIL = '1726850085@qq.com'
|
||||
SUBJECT = '您的验证码'
|
||||
TEMPLATE_ID = 125447 # 替换为实际的模板 ID
|
||||
|
||||
def send_email_with_template(secret_id, secret_key, from_email, to_email, subject, template_id, code):
|
||||
try:
|
||||
# 实例化一个认证对象
|
||||
cred = credential.Credential(secret_id, secret_key)
|
||||
httpProfile = HttpProfile()
|
||||
httpProfile.endpoint = "ses.tencentcloudapi.com"
|
||||
clientProfile = ClientProfile()
|
||||
clientProfile.httpProfile = httpProfile
|
||||
client = ses_client.SesClient(cred, "ap-hongkong", clientProfile)
|
||||
|
||||
# 实例化一个请求对象
|
||||
req = models.SendEmailRequest()
|
||||
params = {
|
||||
"FromEmailAddress": from_email,
|
||||
"Destination": [to_email],
|
||||
"Subject": subject,
|
||||
"ReplyToAddresses": from_email,
|
||||
"Template": {
|
||||
"TemplateID": template_id,
|
||||
"TemplateData": json.dumps({"name": from_email, "code": code})
|
||||
}
|
||||
}
|
||||
req.from_json_string(json.dumps(params))
|
||||
|
||||
# 发送请求并返回响应
|
||||
resp = client.SendEmail(req)
|
||||
return resp.to_json_string()
|
||||
|
||||
except TencentCloudSDKException as err:
|
||||
return str(err)
|
||||
|
||||
def generate_verification_code(length=6):
|
||||
import random
|
||||
import string
|
||||
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
||||
|
||||
def main():
|
||||
code = generate_verification_code()
|
||||
response = send_email_with_template(SECRET_ID, MAIL_SECRET_KEY, EMAIL_HOST_USER, TO_EMAIL, SUBJECT, TEMPLATE_ID, code)
|
||||
print(response)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
57
WebAdmin/membership_rewards.py
Executable file
57
WebAdmin/membership_rewards.py
Executable file
@@ -0,0 +1,57 @@
|
||||
import time
|
||||
from django.http import JsonResponse
|
||||
from django.utils.timezone import now
|
||||
from django.db import connections
|
||||
from datetime import datetime
|
||||
|
||||
# 假设您有一个积分奖励的常量
|
||||
REWARD_POINTS = 100 # 例如100积分
|
||||
|
||||
def reward_points_based_on_membership(openid):
|
||||
"""
|
||||
根据openid查询用户,判断是否是会员,并检查会员是否过期。
|
||||
如果是会员且未过期,则奖励积分。
|
||||
:param openid: 用户的openid
|
||||
:return: JSON响应,包含奖励积分或未满足条件的信息
|
||||
"""
|
||||
# 假设数据库名为 'external_db',其中有 'User' 表
|
||||
with connections['external_db'].cursor() as cursor:
|
||||
# 查询用户是否是会员,以及会员到期时间
|
||||
query = 'SELECT is_member, member_end_time FROM user WHERE openid = %s'
|
||||
cursor.execute(query, [openid])
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
return JsonResponse({'code': 404, 'message': '用户未找到', 'data': {}})
|
||||
|
||||
is_member, member_end_time = result
|
||||
|
||||
# 检查是否为会员且会员未过期
|
||||
if is_member == 1:
|
||||
# 将数据库中的时间戳转换为当前时间戳进行比较
|
||||
current_timestamp = int(time.time()) # 当前时间戳
|
||||
if member_end_time > current_timestamp:
|
||||
# 会员未过期,给予积分奖励
|
||||
# 假设奖励积分逻辑已经实现
|
||||
reward_user_points(openid, REWARD_POINTS)
|
||||
return JsonResponse({'code': 200, 'message': '奖励成功', 'data': {'reward_points': REWARD_POINTS}})
|
||||
else:
|
||||
return JsonResponse({'code': 400, 'message': '会员已过期', 'data': {}})
|
||||
else:
|
||||
return JsonResponse({'code': 400, 'message': '非会员,无积分奖励', 'data': {}})
|
||||
|
||||
def reward_user_points(openid, points):
|
||||
"""
|
||||
给用户奖励积分的逻辑,这里假设直接更新积分字段
|
||||
:param openid: 用户的openid
|
||||
:param points: 奖励的积分数量
|
||||
"""
|
||||
with connections['external_db'].cursor() as cursor:
|
||||
# 假设 User 表有一个 'points' 字段表示用户的积分
|
||||
update_query = """
|
||||
UPDATE user
|
||||
SET points = points + %s
|
||||
WHERE openid = %s
|
||||
"""
|
||||
cursor.execute(update_query, [points, openid])
|
||||
|
||||
15
WebAdmin/middleware.py
Executable file
15
WebAdmin/middleware.py
Executable file
@@ -0,0 +1,15 @@
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth import logout
|
||||
|
||||
class CheckSessionMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
# 检查会话是否过期
|
||||
if request.session.get_expiry_age() <= 0:
|
||||
logout(request) # 注销用户
|
||||
request.session.flush() # 清除会话数据
|
||||
# 这里可以重定向到登录页面或显示会话过期提示
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
124
WebAdmin/migrations/0001_initial.py
Executable file
124
WebAdmin/migrations/0001_initial.py
Executable file
@@ -0,0 +1,124 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-16 15:57
|
||||
|
||||
import django.db.models.deletion
|
||||
from datetime import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("username", models.CharField(max_length=150, unique=True)),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, null=True, unique=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"phone",
|
||||
models.CharField(blank=True, max_length=20, null=True, unique=True),
|
||||
),
|
||||
(
|
||||
"password_hash",
|
||||
models.CharField(blank=True, max_length=128, null=True),
|
||||
),
|
||||
(
|
||||
"google_id",
|
||||
models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("is_member", models.BooleanField(default=False)),
|
||||
("points", models.IntegerField(default=0)),
|
||||
("referral_code", models.CharField(max_length=50, unique=True)),
|
||||
(
|
||||
"commission_rate",
|
||||
models.DecimalField(decimal_places=2, default=0.0, max_digits=5),
|
||||
),
|
||||
("created_at", models.DateTimeField(default=datetime.now)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"invited_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="invitees",
|
||||
to="WebAdmin.user",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Referral",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"commission_amount",
|
||||
models.DecimalField(decimal_places=2, max_digits=10),
|
||||
),
|
||||
("created_at", models.DateTimeField(default=datetime.now)),
|
||||
(
|
||||
"referee",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="referrals_received",
|
||||
to="WebAdmin.user",
|
||||
),
|
||||
),
|
||||
(
|
||||
"referrer",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="referrals_made",
|
||||
to="WebAdmin.user",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="UserSource",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("source", models.CharField(max_length=50)),
|
||||
("created_at", models.DateTimeField(default=datetime.now)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="WebAdmin.user"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
22
WebAdmin/migrations/0002_user_last_login_ip_user_login_count.py
Executable file
22
WebAdmin/migrations/0002_user_last_login_ip_user_login_count.py
Executable file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-16 17:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="last_login_ip",
|
||||
field=models.GenericIPAddressField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="login_count",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
135
WebAdmin/migrations/0003_alter_user_options_alter_user_managers_and_more.py
Executable file
135
WebAdmin/migrations/0003_alter_user_options_alter_user_managers_and_more.py
Executable file
@@ -0,0 +1,135 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-16 18:04
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from datetime import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0002_user_last_login_ip_user_login_count"),
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={"verbose_name": "user", "verbose_name_plural": "users"},
|
||||
),
|
||||
migrations.AlterModelManagers(
|
||||
name="user",
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="password_hash",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="date_joined",
|
||||
field=models.DateTimeField(
|
||||
default=datetime.now, verbose_name="date joined"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="first_name",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=150, verbose_name="first name"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="groups",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="is_staff",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
verbose_name="staff status",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="is_superuser",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="last_login",
|
||||
field=models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="last_name",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=150, verbose_name="last name"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="password",
|
||||
field=models.CharField(default=1, max_length=128, verbose_name="password"),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="user_permissions",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="email",
|
||||
field=models.EmailField(
|
||||
blank=True, default=1, max_length=254, verbose_name="email address"
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="is_active",
|
||||
field=models.BooleanField(
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="username",
|
||||
field=models.CharField(
|
||||
error_messages={"unique": "A user with that username already exists."},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
]
|
||||
48
WebAdmin/migrations/0004_emailverificationcode_smsverificationcode.py
Executable file
48
WebAdmin/migrations/0004_emailverificationcode_smsverificationcode.py
Executable file
@@ -0,0 +1,48 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-20 03:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0003_alter_user_options_alter_user_managers_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EmailVerificationCode",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("email", models.EmailField(max_length=254)),
|
||||
("code", models.CharField(max_length=6)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("is_used", models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SMSVerificationCode",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("phone_number", models.CharField(max_length=15)),
|
||||
("code", models.CharField(max_length=6)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("is_used", models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
60
WebAdmin/migrations/0005_videogeneration.py
Executable file
60
WebAdmin/migrations/0005_videogeneration.py
Executable file
@@ -0,0 +1,60 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 10:13
|
||||
|
||||
import django.db.models.deletion
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0004_emailverificationcode_smsverificationcode"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="VideoGeneration",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("text", models.TextField()),
|
||||
("voice_name", models.CharField(max_length=50)),
|
||||
("style", models.CharField(default="general", max_length=50)),
|
||||
("rate", models.IntegerField(default=0)),
|
||||
("media_type", models.CharField(max_length=50)),
|
||||
("ratio", models.CharField(max_length=10)),
|
||||
("audio_url", models.URLField(blank=True, null=True)),
|
||||
("video_url", models.URLField(blank=True, null=True)),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "Pending"),
|
||||
("in_progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
("failed", "Failed"),
|
||||
],
|
||||
default="pending",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(default=datetime.now)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
18
WebAdmin/migrations/0006_videogeneration_slug.py
Executable file
18
WebAdmin/migrations/0006_videogeneration_slug.py
Executable file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-03 14:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0005_videogeneration"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videogeneration",
|
||||
name="slug",
|
||||
field=models.CharField(default=1, max_length=50),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
37
WebAdmin/migrations/0007_plan.py
Executable file
37
WebAdmin/migrations/0007_plan.py
Executable file
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-24 04:16
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0006_videogeneration_slug"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Plan",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(max_length=100, unique=True)),
|
||||
("description", models.TextField()),
|
||||
("price", models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
("credits_per_month", models.IntegerField(default=0)),
|
||||
("created_at", models.DateTimeField(default=datetime.now)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("is_promotional", models.BooleanField(default=False)),
|
||||
("unlimited_exports", models.BooleanField(default=False)),
|
||||
("smart_music_sync", models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
62
WebAdmin/migrations/0008_order.py
Executable file
62
WebAdmin/migrations/0008_order.py
Executable file
@@ -0,0 +1,62 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-24 12:15
|
||||
|
||||
import django.db.models.deletion
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0007_plan"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Order",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("order_id", models.CharField(max_length=100, unique=True)),
|
||||
("username", models.CharField(max_length=150)),
|
||||
("amount", models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
(
|
||||
"payment_method",
|
||||
models.CharField(
|
||||
choices=[("alipay", "Alipay"), ("paypal", "PayPal")],
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "Pending"),
|
||||
("completed", "Completed"),
|
||||
("canceled", "Canceled"),
|
||||
("failed", "Failed"),
|
||||
],
|
||||
default="pending",
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(default=datetime.now)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
22
WebAdmin/migrations/0009_user_membership_end_user_membership_start.py
Executable file
22
WebAdmin/migrations/0009_user_membership_end_user_membership_start.py
Executable file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-24 13:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0008_order"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="membership_end",
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="membership_start",
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
31
WebAdmin/migrations/0010_alter_order_options_order_plan.py
Executable file
31
WebAdmin/migrations/0010_alter_order_options_order_plan.py
Executable file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-26 13:02
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0009_user_membership_end_user_membership_start"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="order",
|
||||
options={
|
||||
"ordering": ["-created_at"],
|
||||
"verbose_name": "Order",
|
||||
"verbose_name_plural": "Orders",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="order",
|
||||
name="plan",
|
||||
field=models.ForeignKey(
|
||||
default=1,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="WebAdmin.plan",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
17
WebAdmin/migrations/0011_alter_order_amount.py
Executable file
17
WebAdmin/migrations/0011_alter_order_amount.py
Executable file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-26 13:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0010_alter_order_options_order_plan"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="order",
|
||||
name="amount",
|
||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-29 14:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0011_alter_order_amount"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videogeneration",
|
||||
name="video_id",
|
||||
field=models.AutoField(primary_key=True, serialize=False),
|
||||
),
|
||||
]
|
||||
18
WebAdmin/migrations/0012_videogeneration_video_id.py
Executable file
18
WebAdmin/migrations/0012_videogeneration_video_id.py
Executable file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-29 14:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0011_alter_order_amount"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# 这里不再有任何操作,因为我们手动删除了重复的字段添加
|
||||
# migrations.AddField(
|
||||
# model_name="videogeneration",
|
||||
# name="video_id",
|
||||
# field=models.CharField(default="", max_length=255, unique=True),
|
||||
# ),
|
||||
]
|
||||
17
WebAdmin/migrations/0013_videogeneration_video_id.py
Executable file
17
WebAdmin/migrations/0013_videogeneration_video_id.py
Executable file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-29 14:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0012_videogeneration_video_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videogeneration",
|
||||
name="video_id",
|
||||
field=models.CharField(default="", max_length=255, unique=True),
|
||||
),
|
||||
]
|
||||
17
WebAdmin/migrations/0014_videogeneration_pid.py
Executable file
17
WebAdmin/migrations/0014_videogeneration_pid.py
Executable file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.7 on 2024-08-30 02:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0013_videogeneration_video_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videogeneration",
|
||||
name="pid",
|
||||
field=models.CharField(default="", max_length=50, null=True),
|
||||
),
|
||||
]
|
||||
42
WebAdmin/migrations/0015_videogeneration_extension_count_and_more.py
Executable file
42
WebAdmin/migrations/0015_videogeneration_extension_count_and_more.py
Executable file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-14 10:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0014_videogeneration_pid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videogeneration",
|
||||
name="extension_count",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="videogeneration",
|
||||
name="media_type",
|
||||
field=models.CharField(max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="videogeneration",
|
||||
name="rate",
|
||||
field=models.IntegerField(default=0, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="videogeneration",
|
||||
name="style",
|
||||
field=models.CharField(default="general", max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="videogeneration",
|
||||
name="text",
|
||||
field=models.TextField(null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="videogeneration",
|
||||
name="voice_name",
|
||||
field=models.CharField(max_length=50, null=True),
|
||||
),
|
||||
]
|
||||
17
WebAdmin/migrations/0016_videogeneration_time_duration.py
Executable file
17
WebAdmin/migrations/0016_videogeneration_time_duration.py
Executable file
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-14 14:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0015_videogeneration_extension_count_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videogeneration",
|
||||
name="time_duration",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
22
WebAdmin/migrations/0017_user_openid_used_user_source.py
Executable file
22
WebAdmin/migrations/0017_user_openid_used_user_source.py
Executable file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-15 06:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0016_videogeneration_time_duration"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="openid_used",
|
||||
field=models.CharField(blank=True, max_length=150, null=True, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="source",
|
||||
field=models.CharField(default="web", max_length=50),
|
||||
),
|
||||
]
|
||||
77
WebAdmin/migrations/0018_transactionhistory.py
Executable file
77
WebAdmin/migrations/0018_transactionhistory.py
Executable file
@@ -0,0 +1,77 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-15 07:59
|
||||
|
||||
import django.db.models.deletion
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0017_user_openid_used_user_source"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="TransactionHistory",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"feature",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("text-to-video", "AI文生视频"),
|
||||
("img-to-video", "AI图生视频"),
|
||||
("create-tiktok-video", "AI长视频生成"),
|
||||
("music-to-video", "音乐视频生成"),
|
||||
("create-avatar-video", "虚拟形象视频生成"),
|
||||
],
|
||||
max_length=50,
|
||||
verbose_name="功能类型",
|
||||
),
|
||||
),
|
||||
("points_spent", models.IntegerField(verbose_name="消费积分")),
|
||||
(
|
||||
"char_count",
|
||||
models.IntegerField(blank=True, null=True, verbose_name="字符数量"),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.TextField(blank=True, null=True, verbose_name="操作描述"),
|
||||
),
|
||||
(
|
||||
"transaction_date",
|
||||
models.DateTimeField(
|
||||
default=datetime.now, verbose_name="消费时间"
|
||||
),
|
||||
),
|
||||
("previous_points_balance", models.IntegerField(verbose_name="消费前积分")),
|
||||
("new_points_balance", models.IntegerField(verbose_name="消费后积分")),
|
||||
("success", models.BooleanField(default=True, verbose_name="是否成功")),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="transactions",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="用户",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "消费明细",
|
||||
"verbose_name_plural": "消费明细",
|
||||
"db_table": "transaction_history",
|
||||
"ordering": ["-transaction_date"],
|
||||
},
|
||||
),
|
||||
]
|
||||
50
WebAdmin/migrations/0019_websiteinfo.py
Executable file
50
WebAdmin/migrations/0019_websiteinfo.py
Executable file
@@ -0,0 +1,50 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-15 08:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0018_transactionhistory"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="WebsiteInfo",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain",
|
||||
models.CharField(max_length=255, unique=True, verbose_name="域名"),
|
||||
),
|
||||
("title_en", models.CharField(max_length=255, verbose_name="英文标题")),
|
||||
("title_zh", models.CharField(max_length=255, verbose_name="中文标题")),
|
||||
("keywords_en", models.TextField(verbose_name="英文关键词")),
|
||||
("keywords_zh", models.TextField(verbose_name="中文关键词")),
|
||||
("description_en", models.TextField(verbose_name="英文描述")),
|
||||
("description_zh", models.TextField(verbose_name="中文描述")),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "网站信息",
|
||||
"verbose_name_plural": "网站信息",
|
||||
"db_table": "website_info",
|
||||
"ordering": ["-created_at"],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-15 08:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0019_websiteinfo"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="websiteinfo",
|
||||
name="domain",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="websiteinfo",
|
||||
name="domain_en",
|
||||
field=models.CharField(default=True, max_length=255, verbose_name="域名"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="websiteinfo",
|
||||
name="domain_zh",
|
||||
field=models.CharField(default=True, max_length=255, verbose_name="域名"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteinfo",
|
||||
name="description_en",
|
||||
field=models.TextField(default=True, verbose_name="英文描述"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteinfo",
|
||||
name="description_zh",
|
||||
field=models.TextField(default=True, verbose_name="中文描述"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteinfo",
|
||||
name="keywords_en",
|
||||
field=models.TextField(default=True, verbose_name="英文关键词"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteinfo",
|
||||
name="keywords_zh",
|
||||
field=models.TextField(default=True, verbose_name="中文关键词"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteinfo",
|
||||
name="title_en",
|
||||
field=models.CharField(default=True, max_length=255, verbose_name="英文标题"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteinfo",
|
||||
name="title_zh",
|
||||
field=models.CharField(default=True, max_length=255, verbose_name="中文标题"),
|
||||
),
|
||||
]
|
||||
56
WebAdmin/migrations/0021_websiteaccesslog.py
Executable file
56
WebAdmin/migrations/0021_websiteaccesslog.py
Executable file
@@ -0,0 +1,56 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-15 08:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0020_remove_websiteinfo_domain_websiteinfo_domain_en_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="WebsiteAccessLog",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("ip_address", models.GenericIPAddressField(verbose_name="访问IP")),
|
||||
(
|
||||
"browser_language",
|
||||
models.CharField(
|
||||
blank=True, max_length=50, null=True, verbose_name="浏览器语言"
|
||||
),
|
||||
),
|
||||
(
|
||||
"referrer",
|
||||
models.URLField(blank=True, null=True, verbose_name="来源URL"),
|
||||
),
|
||||
("request_path", models.CharField(max_length=255, verbose_name="请求路径")),
|
||||
(
|
||||
"request_method",
|
||||
models.CharField(max_length=10, verbose_name="请求方法"),
|
||||
),
|
||||
(
|
||||
"access_time",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="访问时间"),
|
||||
),
|
||||
(
|
||||
"access_time_bj",
|
||||
models.DateTimeField(blank=True, null=True, verbose_name="北京时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "网站访问日志",
|
||||
"verbose_name_plural": "网站访问日志",
|
||||
"db_table": "website_access_log",
|
||||
"unique_together": {("ip_address", "access_time_bj")},
|
||||
},
|
||||
),
|
||||
]
|
||||
49
WebAdmin/migrations/0022_alter_websiteaccesslog_unique_together_and_more.py
Executable file
49
WebAdmin/migrations/0022_alter_websiteaccesslog_unique_together_and_more.py
Executable file
@@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.0.7 on 2024-09-15 08:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("WebAdmin", "0021_websiteaccesslog"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name="websiteaccesslog",
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="websiteaccesslog",
|
||||
name="access_date",
|
||||
field=models.DateField(blank=True, null=True, verbose_name="访问日期"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="websiteaccesslog",
|
||||
name="device_type",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=50, null=True, verbose_name="设备类型"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="websiteaccesslog",
|
||||
name="user_agent",
|
||||
field=models.TextField(blank=True, null=True, verbose_name="用户代理"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteaccesslog",
|
||||
name="browser_language",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=200, null=True, verbose_name="浏览器语言"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="websiteaccesslog",
|
||||
name="referrer",
|
||||
field=models.TextField(blank=True, null=True, verbose_name="来源URL"),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="websiteaccesslog",
|
||||
unique_together={("ip_address", "access_date")},
|
||||
),
|
||||
]
|
||||
0
WebAdmin/migrations/__init__.py
Executable file
0
WebAdmin/migrations/__init__.py
Executable file
BIN
WebAdmin/migrations/__pycache__/0001_initial.cpython-310.pyc
Executable file
BIN
WebAdmin/migrations/__pycache__/0001_initial.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
WebAdmin/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user