lty/qy_lty/qy_lty/settings.py
pmc c0fe1f502b
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 1h5m35s
feat: update card models, admin pages, and add migrations
- Update card models, serializers, views and URLs
- Update dances, songs, users admin pages and API modules
- Add card migrations (merge furniture into decoration)
- Update middleware and settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 16:38:48 +08:00

660 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

"""
Django settings for qy_lty project.
Generated by 'django-admin startproject' using Django 4.2.13.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
from decouple import config, Csv
from common.aliyun_logging import setup_logging
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True # 开发环境下设置为True
ALLOWED_HOSTS = ['*'] # 开发环境下允许所有主机
# Disable CSRF protection globally
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
CSRF_USE_SESSIONS = False
CSRF_COOKIE_AGE = None
# Application definition
INSTALLED_APPS = [
'rosetta',
'simpleui',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites', # required for django-allauth
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.weixin', # 微信提供商
'rest_framework',
'drf_yasg',
'rest_framework.authtoken', # required for DRF token authentication
'dj_rest_auth',
'phone_verify',
'userapp',
'aiapp',
'ali_vi_app',
'subscription_app',
'corsheaders', # 添加CORS支持
'debug_toolbar',
'channels',
'device_interaction',
'card',
'workflow_app',
'achievement_app', # 成就系统应用
'food_app', # 食物管理应用
]
AUTH_USER_MODEL = 'userapp.ParadiseUser'
# REST_AUTH_TOKEN_MODEL = 'userapp.UserToken'
SITE_ID = 1
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # CORS中间件必须放在CommonMiddleware之前
'django.middleware.locale.LocaleMiddleware', # 添加语言中间件
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'common.middleware.StandardResponseMiddleware', # 添加标准响应中间件
]
# CORS设置
CORS_ALLOW_ALL_ORIGINS = True # 允许所有来源的跨域请求,适用于开发环境
# 生产环境建议使用具体的白名单
# CORS_ALLOWED_ORIGINS = [
# "https://example.com",
# "https://sub.example.com",
# "http://localhost:8080",
# "http://127.0.0.1:8000",
# ]
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
CORS_ALLOW_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
]
CORS_ALLOW_CREDENTIALS = True # 允许携带Cookie
ROOT_URLCONF = 'qy_lty.urls'
# 仅在调试模式下启用
INTERNAL_IPS = [
'127.0.0.1',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 'DIRS': [os.path.join(BASE_DIR, 'templates')],
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'qy_lty.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': config('MYSQL_DATABASE_NAME'),
# 'USER': config('MYSQL_DATABASE_USER'),
# 'PASSWORD': config('MYSQL_DATABASE_PASSWORD'),
# 'HOST': config('MYSQL_DATABASE_HOST'),
# 'PORT': config('MYSQL_DATABASE_PORT')
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('POSTGRESQL_DATABASE_NAME'),
'USER': config('POSTGRESQL_DATABASE_USER'),
'PASSWORD': config('POSTGRESQL_DATABASE_PASSWORD'),
'HOST': config('POSTGRESQL_DATABASE_HOST'),
'PORT': config('POSTGRESQL_DATABASE_PORT')
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
TIME_ZONE = 'Asia/Shanghai'
# 语言设置
LANGUAGE_CODE = 'zh-hans'
USE_I18N = True # 启用国际化
USE_L10N = True # 启用本地化
USE_TZ = True
# 语言设置
# LANGUAGES = [
# ('zh-hans', '简体中文'),
# ('en', 'English'),
# # ('fr', 'French'),
# # 添加更多语言...
# ]
# 语言翻译文件路径
# LOCALE_PATHS = [
# os.path.join(BASE_DIR, 'locale'), # 将翻译文件存放在 locale 文件夹中
# ]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = '/static/'
# 静态文件收集目录
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# 额外的静态文件目录
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
# 静态文件存储后端
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 火山引擎配置
VOLCENGINE_APP_ID = config('VOLCENGINE_APP_ID', default='')
VOLCENGINE_APP_KEY = config('VOLCENGINE_APP_KEY', default='')
VOLCENGINE_TOKEN_EXPIRE_TIME = config('VOLCENGINE_TOKEN_EXPIRE_TIME', default=30 * 24 * 3600, cast=int) # 默认30天
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# 配置Redis缓存
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_LOCATION'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': config('REDIS_PASSWORD'),
}
}
}
# 配置DRF
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10000/second', # 未认证用户每秒最多10000次请求
'user': '20000/second', # 已认证用户每秒最多20000次请求
'api_path': '10000/second', # 为 /api/ 路径的请求设置限流速率
},
'DEFAULT_PAGINATION_CLASS': 'common.pagination.CustomPageNumberPagination',
'PAGE_SIZE': 10,
'UNICODE_JSON': False,
}
# Disable CSRF checks for API views
from rest_framework.authentication import SessionAuthentication
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
# Add to your settings
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = (
# 'userapp.settings.CsrfExemptSessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
)
# Aliyun SMS configuration
ALIYUN_SMS_ACCESS_KEY_ID = config('ALIYUN_SMS_ACCESS_KEY_ID')
ALIYUN_SMS_ACCESS_KEY_SECRET = config('ALIYUN_SMS_ACCESS_KEY_SECRET')
ALIYUN_SMS_SIGN_NAME = config('ALIYUN_SMS_SIGN_NAME')
ALIYUN_SMS_TEMPLATE_CODE = config('ALIYUN_SMS_TEMPLATE_CODE')
PHONE_VERIFICATION = {
'BACKEND': 'phone_verify.backends.twilio.TwilioBackend',
'OPTIONS': {
'SID': 'your_twilio_sid',
'SECRET': 'your_twilio_secret',
'FROM': '+1234567890', # your Twilio phone number
'SANDBOX_TOKEN': 'your_sandbox_token',
},
'TOKEN_LENGTH': 6,
'MESSAGE': 'Welcome to MyProject! Your verification code is: {code}',
'APP_NAME': 'MyProject',
'SECURITY_CODE_EXPIRATION_TIME': 3600, # 1 hour
'VERIFY_SECURITY_CODE_ONLY_ONCE': False,
}
# 配置django-allauth
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
ACCOUNT_LOGIN_METHODS = {'username'}
ACCOUNT_SIGNUP_FIELDS = ['username*', 'password1*', 'password2*']
ACCOUNT_EMAIL_VERIFICATION = 'optional'
# 其他django-allauth设置根据需求配置
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
# dj-rest-auth 使用自定义的注册序列化器
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'userapp.serializers.CustomRegisterSerializer',
}
# dj-rest-auth 使用自定义用户详情序列化器
REST_AUTH_SERIALIZERS = {
'USER_DETAILS_SERIALIZER': 'userapp.serializers.CustomUserDetailsSerializer',
}
# kimi设置
KIMI_API_KEY = config('KIMI_API_KEY')
KIMI_BASE_URL = config('KIMI_BASE_URL', default='https://api.moonshot.cn/v1')
# # 日志设置
# LOGGING = {
# 'version': 1,
# 'disable_existing_loggers': False,
# 'handlers': {
# 'console': {
# 'class': 'logging.StreamHandler',
# },
# },
# 'loggers': {
# '': {
# 'handlers': ['console'],
# 'level': 'DEBUG',
# 'propagate': True,
# },
# },
# }
# 日志设置(阿里云)
setup_logging()
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'aliyun': {
'level': 'INFO',
'class': 'common.aliyun_logging.AliyunLogHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['aliyun', 'console'],
'level': 'INFO',
'propagate': True,
},
'django.request': {
'handlers': ['console'],
'level': 'ERROR',
'propagate': False,
},
'aiapp': {
'handlers': ['aliyun', 'console'],
'level': 'INFO',
'propagate': True,
},
'common': {
'handlers': ['aliyun', 'console'],
'level': 'INFO',
'propagate': True,
},
'userapp': {
'handlers': ['aliyun', 'console'],
'level': 'INFO',
'propagate': True,
},
},
}
# 语音服务
AUDIO_SERVICE_PROVIDER = 'huoshan' # 'aliyun' 或 'tencent' 或 'huoshan'
AUDIO_SERVICE_CONFIG = {
'aliyun': {
'api_key': config('ALIYUN_NLS_ACCESS_KEY_ID'),
'api_secret': config('ALIYUN_NLS_ACCESS_KEY_SECRET'),
'app_id': config('ALIYUN_NLS_APP_ID'),
'oss_key_id': config('ALIYUN_OSS_ACCESS_KEY_ID'),
'oss_key_secret': config('ALIYUN_OSS_ACCESS_KEY_SECRET'),
'oss_bucket': config('ALIYUN_OSS_BUCKET'),
'oss_endpoint': config('ALIYUN_OSS_ENDPOINT'),
'oss_host': config('ALIYUN_OSS_HOST'),
'oss_audio_base_dir': config('ALIYUN_OSS_AUDIO_BASE_DIR')
},
'tencent': {
'api_key': config('AUDIO_SERVICE_TENCENT_API_KEY'),
'api_secret': config('AUDIO_SERVICE_TENCENT_API_SECRET')
},
'huoshan': {
'appid': config('AUDIO_SERVICE_HUOSHAN_APPID'),
'access_token': config('AUDIO_SERVICE_HUOSHAN_ACCESS_TOKEN'),
'cluster': config('AUDIO_SERVICE_HUOSHAN_CLUSTER'),
'voice_type': config('AUDIO_SERVICE_HUOSHAN_VOICE_TYPE'),
'storage_dir': config('AUDIO_SERVICE_HUOSHAN_STORAGE_DIR'),
'base_url': config('AUDIO_SERVICE_HUOSHAN_BASE_URL'),
# 阿里云语音识别配置
'aliyun_asr': {
'api_key': config('ALIYUN_NLS_ACCESS_KEY_ID'),
'api_secret': config('ALIYUN_NLS_ACCESS_KEY_SECRET'),
'app_id': config('ALIYUN_NLS_APP_ID'),
# 添加OSS相关配置用于AliyunAudioService初始化
'oss_key_id': config('ALIYUN_OSS_ACCESS_KEY_ID'),
'oss_key_secret': config('ALIYUN_OSS_ACCESS_KEY_SECRET'),
'oss_bucket': config('ALIYUN_OSS_BUCKET'),
'oss_endpoint': config('ALIYUN_OSS_ENDPOINT'),
'oss_host': config('ALIYUN_OSS_HOST'),
'oss_audio_base_dir': config('ALIYUN_OSS_AUDIO_BASE_DIR')
}
},
}
# ali vi
ALIYUN_VI_ACCESS_KEY_ID = config('ALIYUN_VI_ACCESS_KEY_ID')
ALIYUN_VI_ACCESS_KEY_SECRET = config('ALIYUN_VI_ACCESS_KEY_SECRET')
ALIYUN_VI_ENDPOINT = config('ALIYUN_VI_ENDPOINT')
ALIYUN_VI_REGION = config('ALIYUN_VI_REGION')
# SimpleUI 基本设置
SIMPLEUI_DEFAULT_THEME = 'admin.lte.css'
SIMPLEUI_ANALYSIS = False
SIMPLEUI_STATIC_OFFLINE = True
SIMPLEUI_HOME_INFO = False
SIMPLEUI_LOGIN_PARTICLES = False
SIMPLEUI_LOADING = False
# 移除不必要的 SimpleUI 配置
SIMPLEUI_HOME_QUICK = None
SIMPLEUI_HOME_ACTION = False
SIMPLEUI_CONFIG = None
# SimpleUI 设置
SIMPLEUI_DEFAULT_THEME = 'admin.lte.css'
SIMPLEUI_ANALYSIS = False
SIMPLEUI_HOME_INFO = False
SIMPLEUI_ICON = {
'auth': {'name': '认证与授权', 'icon': 'fas fa-user-shield'},
'auth.user': {'name': '用户', 'icon': 'fas fa-user'},
'auth.Group': {'name': '用户组', 'icon': 'fas fa-users'},
'userapp.ParadiseUser': {'name': '用户管理', 'icon': 'fas fa-user-circle'},
'card.CardCategory': {'name': '卡片类别', 'icon': 'fas fa-tags'},
'card.Card': {'name': '卡片', 'icon': 'fas fa-credit-card'},
'card.CardBatch': {'name': '卡片批次', 'icon': 'fas fa-layer-group'},
'card.CardUsageLog': {'name': '使用记录', 'icon': 'fas fa-history'},
'AI': {'name': '人工智能', 'icon': 'fas fa-robot'},
'Card': {'name': '卡片系统', 'icon': 'fas fa-credit-card'},
'Phone Verification': {'name': '手机验证', 'icon': 'fas fa-mobile-alt'},
'subscription_app': {'name': '订阅管理', 'icon': 'fas fa-receipt'},
'Userapp': {'name': '用户系统', 'icon': 'fas fa-users-cog'},
'sites': {'name': '站点管理', 'icon': 'fas fa-globe'},
'device_interaction': {'name': '设备交互', 'icon': 'fas fa-laptop'},
}
# 设置 SimpleUI 的语言
SIMPLEUI_CHINESE = True
# Channels 配置
ASGI_APPLICATION = 'qy_lty.asgi.application'
# Channel Layers 配置(使用 Redis
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [f"redis://:{config('REDIS_PASSWORD')}@{config('REDIS_LOCATION').replace('redis://', '')}"],
},
},
}
SPECTACULAR_SETTINGS = {
'TITLE': 'Fengye Website API',
'DESCRIPTION': 'API documentation for Fengye Website including WebSocket endpoints',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
'persistAuthorization': True,
},
'TAGS': [
{'name': 'websocket', 'description': 'WebSocket endpoints'},
{'name': 'http', 'description': 'HTTP endpoints'},
],
}
# Swagger settings
SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': False,
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header',
'description': '输入格式: Bearer {token}'
}
},
'VALIDATOR_URL': None,
'TAGS_SORTER': 'alpha',
'DOC_EXPANSION': 'list',
'DEFAULT_MODEL_RENDERING': 'model',
'OPERATIONS_SORTER': 'alpha',
'TAGS': [
{'name': '用户认证', 'description': '用户登录、注册、认证相关API'},
{'name': '用户管理', 'description': '用户信息管理API'},
{'name': 'AI聊天', 'description': 'AI对话和多轮对话功能'},
{'name': '卡片管理', 'description': '卡片模板和批次管理'},
{'name': '卡片使用', 'description': '卡片的扫描、使用及查询'},
{'name': '图像处理', 'description': '基于阿里云视觉智能的图像处理API'},
{'name': 'device', 'description': '设备交互API'},
]
}
# Swagger/OpenAPI settings
SWAGGER_SETTINGS['USE_SESSION_AUTH'] = False
SWAGGER_SETTINGS['PERSIST_AUTH'] = True
SWAGGER_SETTINGS['REFETCH_SCHEMA_WITH_AUTH'] = True
SWAGGER_SETTINGS['REFETCH_SCHEMA_ON_LOGOUT'] = True
# 设置 API URL
if DEBUG:
SWAGGER_SETTINGS['DEFAULT_INFO'] = 'qy_lty.urls.api_info'
SWAGGER_SETTINGS['SCHEME'] = 'http'
SWAGGER_SETTINGS['DEFAULT_API_URL'] = 'http://localhost:8000'
# Debug Toolbar settings
if DEBUG:
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': lambda request: True,
'INTERCEPT_REDIRECTS': False,
}
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.versions.VersionsPanel',
'debug_toolbar.panels.timer.TimerPanel',
'debug_toolbar.panels.settings.SettingsPanel',
'debug_toolbar.panels.headers.HeadersPanel',
'debug_toolbar.panels.request.RequestPanel',
'debug_toolbar.panels.sql.SQLPanel',
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
'debug_toolbar.panels.templates.TemplatesPanel',
'debug_toolbar.panels.cache.CachePanel',
'debug_toolbar.panels.signals.SignalsPanel',
'debug_toolbar.panels.logging.LoggingPanel',
'debug_toolbar.panels.redirects.RedirectsPanel',
]
# 火山引擎RTC ak和sk配置(风也账户)
VOLCENGINE_ACCESS_KEY = config('VOLCENGINE_ACCESS_KEY')
VOLCENGINE_SECRET_KEY = config('VOLCENGINE_SECRET_KEY')
def print_database_and_cache_info():
"""Print database and cache configuration information"""
import logging
import time
from django.db import connection
from django.core.cache import cache
logger = logging.getLogger('django')
# Get database configuration
db_config = DATABASES['default']
db_info = f"""
Database Configuration:
Type: {db_config['ENGINE']}
Host: {db_config['HOST']}
Port: {db_config['PORT']}
Database: {db_config['NAME']}
User: {db_config['USER']}
"""
# Get cache configuration
cache_config = CACHES['default']
cache_info = f"""
Cache Configuration:
Type: {cache_config['BACKEND']}
Location: {cache_config['LOCATION']}
"""
# Test database connection and latency
try:
start_time = time.time()
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
db_latency = (time.time() - start_time) * 1000 # Convert to milliseconds
db_status = "OK"
except Exception as e:
db_latency = "N/A"
db_status = f"Error: {str(e)}"
# Test cache connection and latency
try:
start_time = time.time()
test_key = "connection_test"
cache.set(test_key, "test", 1)
cache.get(test_key)
cache_latency = (time.time() - start_time) * 1000 # Convert to milliseconds
cache_status = "OK"
except Exception as e:
cache_latency = "N/A"
cache_status = f"Error: {str(e)}"
# Output information
logger.info("="*50)
logger.info("Application Startup Configuration")
logger.info("="*50)
logger.info(db_info)
logger.info(f"Database Status: {db_status}")
logger.info(f"Database Latency: {db_latency if isinstance(db_latency, str) else f'{db_latency:.2f}'}ms")
logger.info(cache_info)
logger.info(f"Cache Status: {cache_status}")
logger.info(f"Cache Latency: {cache_latency if isinstance(cache_latency, str) else f'{cache_latency:.2f}'}ms")
logger.info("="*50)
# Call on application startup
print_database_and_cache_info()