You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DjangoBlog/djangoblog/settings.py

384 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
Django settings for djangoblog project.
Generated by 'django-admin startproject' using Django 1.10.2.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
import sys
from pathlib import Path
from django.utils.translation import gettext_lazy as _
def env_to_bool(env, default):
"""将环境变量值转换为布尔值的工具函数"""
str_val = os.environ.get(env)
return default if str_val is None else str_val == 'True'
#姜雨菲: 构建项目路径BASE_DIR为项目根目录
BASE_DIR = Path(__file__).resolve().parent.parent
#姜雨菲: 快速开发设置 - 不适用于生产环境
# 安全警告生产环境中请保持SECRET_KEY的机密性
SECRET_KEY = os.environ.get(
'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'
# 安全警告生产环境中请关闭DEBUG模式
DEBUG = env_to_bool('DJANGO_DEBUG', True)
# 测试环境标识当执行测试命令时为True
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
# 允许访问的主机,生产环境需配置具体域名
ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com']
# Django 4.0新增配置指定可信任的CSRF来源
CSRF_TRUSTED_ORIGINS = ['http://example.com']
# 应用定义
INSTALLED_APPS = [
# 自定义的Admin配置简化版
'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.auth', # 用户认证应用
'django.contrib.contenttypes', # 内容类型框架
'django.contrib.sessions', # 会话框架
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件管理
'django.contrib.sites', # 站点框架(用于多站点管理)
'django.contrib.sitemaps', # 站点地图框架
'mdeditor', # Markdown编辑器应用
'haystack', # 搜索框架
'blog', # 博客应用
'accounts', # 用户账户应用
'comments', # 评论应用
'oauth', # 第三方登录应用
'servermanager', # 服务器管理应用
'owntracks', # 位置追踪应用
'compressor', # 静态文件压缩应用
'djangoblog' # 项目主应用
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # 安全中间件处理HTTPS等安全相关
'django.contrib.sessions.middleware.SessionMiddleware', # 会话中间件
'django.middleware.locale.LocaleMiddleware', # 国际化中间件(语言切换)
'django.middleware.gzip.GZipMiddleware', # GZip压缩中间件
'django.middleware.common.CommonMiddleware', # 通用中间件(处理请求/响应)
'django.middleware.csrf.CsrfViewMiddleware', # CSRF保护中间件
'django.contrib.auth.middleware.AuthenticationMiddleware', # 认证中间件
'django.contrib.messages.middleware.MessageMiddleware', # 消息中间件
'django.middleware.clickjacking.XFrameOptionsMiddleware', # 点击劫持保护中间件
'django.middleware.http.ConditionalGetMiddleware', # 条件获取中间件处理304响应
'blog.middleware.OnlineMiddleware' # 自定义在线用户统计中间件
]
ROOT_URLCONF = 'djangoblog.urls' # 项目URL配置入口
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates', # 模板引擎
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 自定义模板目录
'APP_DIRS': True, # 是否自动搜索应用内的templates目录
'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',
'blog.context_processors.seo_processor' # 自定义SEO上下文处理器
],
},
},
]
WSGI_APPLICATION = 'djangoblog.wsgi.application' # WSGI应用入口
# 数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 使用MySQL数据库引擎
'NAME': 'djangoblog', # 数据库名
'USER': 'root', # 数据库用户名
'PASSWORD': '050807', # 数据库密码
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
}
}
# 密码验证配置
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',
},
]
# 支持的语言列表(国际化配置)
LANGUAGES = (
('en', _('English')),
('zh-hans', _('Simplified Chinese')),
('zh-hant', _('Traditional Chinese')),
)
# 语言文件路径
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
LANGUAGE_CODE = 'zh-hans' # 默认语言
TIME_ZONE = 'Asia/Shanghai' # 时区
USE_I18N = True # 启用国际化
USE_L10N = True # 启用本地化格式
USE_TZ = False # 不使用时区感知模型
# 搜索框架Haystack配置
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', # 使用Whoosh搜索引擎中文适配版
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), # 索引存储路径
},
}
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # 实时信号处理器(自动更新索引)
# 认证后端配置(支持用户名或邮箱登录)
AUTHENTICATION_BACKENDS = [
'accounts.user_login_backend.EmailOrUsernameModelBackend']
# 静态文件配置
STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') # 静态文件收集目录(生产环境用)
STATIC_URL = '/static/' # 静态文件URL前缀
STATICFILES = os.path.join(BASE_DIR, 'static') # 静态文件源目录
# 插件静态文件目录
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'plugins'), # 插件静态文件目录
]
AUTH_USER_MODEL = 'accounts.BlogUser' # 自定义用户模型
LOGIN_URL = '/login/' # 登录URL
# 时间格式定义
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
DATE_TIME_FORMAT = '%Y-%m-%d'
# Bootstrap颜色样式
BOOTSTRAP_COLOR_TYPES = [
'default', 'primary', 'success', 'info', 'warning', 'danger'
]
# 分页配置
PAGINATE_BY = 10
# HTTP缓存超时时间
CACHE_CONTROL_MAX_AGE = 2592000
# 缓存配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 本地内存缓存
'TIMEOUT': 10800, # 缓存超时时间(秒)
'LOCATION': 'unique-snowflake', # 缓存位置标识
}
}
# 若存在环境变量则使用Redis作为缓存
if os.environ.get("DJANGO_REDIS_URL"):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}',
}
}
SITE_ID = 1 # 站点ID多站点时使用
# 百度链接提交通知URL
BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \
or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn'
# 邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # SMTP邮件后端
EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) # 是否使用TLS
EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) # 是否使用SSL
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com' # 邮件服务器
EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465) # 邮件端口
EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') # 邮件用户名
EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') # 邮件密码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER # 默认发件人
SERVER_EMAIL = EMAIL_HOST_USER # 服务器邮件发件人
# 管理员邮件通知配置
ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')]
# 微信管理密码两次MD5加密
WXADMIN = os.environ.get(
'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7'
# 日志配置
LOG_PATH = os.path.join(BASE_DIR, 'logs')
if not os.path.exists(LOG_PATH):
os.makedirs(LOG_PATH, exist_ok=True)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'root': {
'level': 'INFO',
'handlers': ['console', 'log_file'],
},
'formatters': {
'verbose': {
'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s',
}
},
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'log_file': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(LOG_PATH, 'djangoblog.log'),
'when': 'D',
'formatter': 'verbose',
'interval': 1,
'delay': True,
'backupCount': 5,
'encoding': 'utf-8'
},
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
'null': {
'class': 'logging.NullHandler',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'djangoblog': {
'handlers': ['log_file', 'console'],
'level': 'INFO',
'propagate': True,
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
}
}
}
# 静态文件查找器用于Compressor
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
)
COMPRESS_ENABLED = True # 启用静态文件压缩
# 根据环境变量决定是否启用离线压缩
COMPRESS_OFFLINE = os.environ.get('COMPRESS_OFFLINE', 'False').lower() == 'true'
COMPRESS_OUTPUT_DIR = 'compressed' # 压缩文件输出目录
COMPRESS_CSS_HASHING_METHOD = 'mtime' # CSS哈希生成方式基于修改时间
COMPRESS_JS_HASHING_METHOD = 'mtime' # JS哈希生成方式基于修改时间
# CSS压缩过滤器
COMPRESS_CSS_FILTERS = [
'compressor.filters.css_default.CssAbsoluteFilter', # 处理CSS中的绝对URL
'compressor.filters.cssmin.CSSCompressorFilter', # CSS压缩器
]
# JS压缩过滤器
COMPRESS_JS_FILTERS = [
'compressor.filters.jsmin.SlimItFilter', # JS压缩器
]
COMPRESS_CACHE_BACKEND = 'default' # 压缩缓存后端
COMPRESS_CACHE_KEY_FUNCTION = 'compressor.cache.simple_cachekey' # 缓存键生成函数
# 预编译器配置支持SCSS/SASS
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'),
('text/x-sass', 'django_libsass.SassCompiler'),
)
# 压缩性能优化配置
COMPRESS_MINT_DELAY = 30
COMPRESS_MTIME_DELAY = 10
COMPRESS_REBUILD_TIMEOUT = 2592000
COMPRESS_CSS_COMPRESSOR = 'compressor.css.CssCompressor'
COMPRESS_JS_COMPRESSOR = 'compressor.js.JsCompressor'
# 静态文件存储带Manifest用于缓存破坏
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
COMPRESS_URL = STATIC_URL
COMPRESS_ROOT = STATIC_ROOT
# 媒体文件(用户上传文件)配置
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
MEDIA_URL = '/media/'
# XFrameOptions配置允许同域iframe
X_FRAME_OPTIONS = 'SAMEORIGIN'
# 安全头部配置
SECURE_BROWSER_XSS_FILTER = True # 启用XSS过滤
SECURE_CONTENT_TYPE_NOSNIFF = True # 禁止内容类型嗅探
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin' # Referrer策略
# 内容安全策略CSP
CSP_DEFAULT_SRC = ["'self'"]
CSP_SCRIPT_SRC = ["'self'", "'unsafe-inline'", "cdn.mathjax.org", "*.googleapis.com"]
CSP_STYLE_SRC = ["'self'", "'unsafe-inline'", "*.googleapis.com", "*.gstatic.com"]
CSP_IMG_SRC = ["'self'", "data:", "*.lylinux.net", "*.gravatar.com", "*.githubusercontent.com"]
CSP_FONT_SRC = ["'self'", "*.googleapis.com", "*.gstatic.com"]
CSP_CONNECT_SRC = ["'self'"]
CSP_FRAME_SRC = ["'none'"]
CSP_OBJECT_SRC = ["'none'"]
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # 默认自增字段类型
# 若存在环境变量则使用Elasticsearch作为搜索后端
if os.environ.get('DJANGO_ELASTICSEARCH_HOST'):
ELASTICSEARCH_DSL = {
'default': {
'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST')
},
}
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine',
},
}
# 插件系统配置
PLUGINS_DIR = BASE_DIR / 'plugins'
ACTIVE_PLUGINS = [
'article_copyright', # 文章版权插件
'reading_time', # 阅读时间插件
'external_links', # 外部链接插件
'view_count', # 阅读计数插件
'seo_optimizer', # SEO优化插件
'image_lazy_loading', # 图片懒加载插件
'article_recommendation', # 文章推荐插件
]