chore(settings): 建立第一阶段分环境工程基线
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
# Django 运行环境
|
||||||
|
DJANGO_SETTINGS_MODULE=JobRadar.settings.development
|
||||||
|
DJANGO_SECRET_KEY=请使用随机生成的长密钥替换此占位符
|
||||||
|
DJANGO_DEBUG=true
|
||||||
|
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
|
||||||
|
DJANGO_CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8000,http://localhost:8000
|
||||||
|
|
||||||
|
# 数据库:开发环境默认 sqlite;集成及生产环境使用 postgresql
|
||||||
|
DATABASE_ENGINE=sqlite
|
||||||
|
POSTGRES_DB=jobradar
|
||||||
|
POSTGRES_USER=jobradar
|
||||||
|
POSTGRES_PASSWORD=请替换为数据库密码
|
||||||
|
POSTGRES_HOST=127.0.0.1
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_SSLMODE=prefer
|
||||||
|
POSTGRES_CONN_MAX_AGE=60
|
||||||
|
|
||||||
|
# OpenAI Agents SDK:第一阶段仅预留配置,不执行真实模型调用
|
||||||
|
OPENAI_API_KEY=请替换为OpenAI_API_Key
|
||||||
|
OPENAI_MODEL=
|
||||||
|
|
||||||
|
# 生产安全配置
|
||||||
|
DJANGO_SECURE_SSL_REDIRECT=true
|
||||||
|
DJANGO_SECURE_HSTS_SECONDS=31536000
|
||||||
|
DJANGO_SECURE_HSTS_PRELOAD=false
|
||||||
@@ -31,9 +31,14 @@ htmlcov/
|
|||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
*.sqlite3
|
||||||
|
*.sqlite3-journal
|
||||||
*.log
|
*.log
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
|
# 自动化测试生成的临时媒体文件
|
||||||
|
.test-media/
|
||||||
|
|
||||||
# IDE 与编辑器
|
# IDE 与编辑器
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
+2
-1
@@ -11,6 +11,7 @@ import os
|
|||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'JobRadar.settings')
|
# 本地直接启动时使用开发配置;部署环境应在进程配置中显式覆盖。
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "JobRadar.settings.development")
|
||||||
|
|
||||||
application = get_asgi_application()
|
application = get_asgi_application()
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for JobRadar project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 6.0.8.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/6.0/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/6.0/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# 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/6.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'django-insecure-ji%iqjdfj*vv!93#t2+wygz8qe@i_b&hss9$%3@rv)cf(0bp8%'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'JobRadar.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [BASE_DIR / 'templates']
|
|
||||||
,
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'JobRadar.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/6.0/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/6.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/6.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
"""JobRadar 分环境配置包。"""
|
||||||
|
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
"""JobRadar 所有运行环境共享的基础配置。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# 项目根目录。所有本地文件路径统一从这里派生,避免依赖启动目录。
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def env(name: str, default: str | None = None) -> str | None:
|
||||||
|
"""读取环境变量;空字符串按未配置处理,避免误用无效密钥。"""
|
||||||
|
|
||||||
|
value = os.environ.get(name, default)
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.strip()
|
||||||
|
return value or default
|
||||||
|
|
||||||
|
|
||||||
|
def env_bool(name: str, default: bool = False) -> bool:
|
||||||
|
"""读取布尔环境变量,并拒绝含义不明确的值。"""
|
||||||
|
|
||||||
|
value = env(name)
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
normalized = value.lower()
|
||||||
|
if normalized in {"1", "true", "yes", "on"}:
|
||||||
|
return True
|
||||||
|
if normalized in {"0", "false", "no", "off"}:
|
||||||
|
return False
|
||||||
|
raise ValueError(f"环境变量 {name} 必须是布尔值,当前值无法识别。")
|
||||||
|
|
||||||
|
|
||||||
|
def env_list(name: str, default: tuple[str, ...] = ()) -> list[str]:
|
||||||
|
"""将逗号分隔的环境变量转换为已去空白的列表。"""
|
||||||
|
|
||||||
|
value = env(name)
|
||||||
|
if value is None:
|
||||||
|
return list(default)
|
||||||
|
return [item.strip() for item in value.split(",") if item.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "JobRadar.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [BASE_DIR / "templates"],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "JobRadar.wsgi.application"
|
||||||
|
ASGI_APPLICATION = "JobRadar.asgi.application"
|
||||||
|
|
||||||
|
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"},
|
||||||
|
]
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "zh-hans"
|
||||||
|
TIME_ZONE = "Asia/Shanghai"
|
||||||
|
USE_I18N = True
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||||
|
MEDIA_URL = "media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
LOGIN_URL = "login"
|
||||||
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
LOGOUT_REDIRECT_URL = "login"
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""本地开发配置:默认使用 SQLite,允许按需连接 PostgreSQL。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import secrets
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from .base import BASE_DIR, env, env_bool, env_list
|
||||||
|
from .base import * # noqa: F403
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG = env_bool("DJANGO_DEBUG", default=True)
|
||||||
|
ALLOWED_HOSTS = env_list(
|
||||||
|
"DJANGO_ALLOWED_HOSTS",
|
||||||
|
default=("127.0.0.1", "localhost", "[::1]"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 开发环境未注入密钥时生成进程级临时密钥,避免在仓库中保留固定密钥。
|
||||||
|
SECRET_KEY = env("DJANGO_SECRET_KEY")
|
||||||
|
if not SECRET_KEY:
|
||||||
|
SECRET_KEY = secrets.token_urlsafe(50)
|
||||||
|
warnings.warn(
|
||||||
|
"未配置 DJANGO_SECRET_KEY,当前开发进程使用临时密钥;重启后已有会话会失效。",
|
||||||
|
RuntimeWarning,
|
||||||
|
stacklevel=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
database_engine = env("DATABASE_ENGINE", "sqlite").lower()
|
||||||
|
if database_engine == "sqlite":
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elif database_engine in {"postgres", "postgresql"}:
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"NAME": env("POSTGRES_DB", "jobradar"),
|
||||||
|
"USER": env("POSTGRES_USER", "jobradar"),
|
||||||
|
"PASSWORD": env("POSTGRES_PASSWORD", ""),
|
||||||
|
"HOST": env("POSTGRES_HOST", "127.0.0.1"),
|
||||||
|
"PORT": env("POSTGRES_PORT", "5432"),
|
||||||
|
"CONN_MAX_AGE": 0,
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslmode": env("POSTGRES_SSLMODE", "prefer"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
raise ValueError("DATABASE_ENGINE 仅支持 sqlite、postgres 或 postgresql。")
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""PostgreSQL 集成验证配置,只允许显式提供测试数据库凭据。"""
|
||||||
|
|
||||||
|
from .base import env, env_list
|
||||||
|
from .base import * # noqa: F403
|
||||||
|
|
||||||
|
|
||||||
|
def required_env(name: str) -> str:
|
||||||
|
"""读取必填配置;缺失时立即失败,防止误连其他数据库。"""
|
||||||
|
|
||||||
|
value = env(name)
|
||||||
|
if not value:
|
||||||
|
raise RuntimeError(f"PostgreSQL 集成验证缺少必填环境变量:{name}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
SECRET_KEY = env("DJANGO_SECRET_KEY", "integration-test-only-secret-key")
|
||||||
|
DEBUG = False
|
||||||
|
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", default=("localhost",))
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"NAME": required_env("POSTGRES_DB"),
|
||||||
|
"USER": required_env("POSTGRES_USER"),
|
||||||
|
"PASSWORD": required_env("POSTGRES_PASSWORD"),
|
||||||
|
"HOST": required_env("POSTGRES_HOST"),
|
||||||
|
"PORT": env("POSTGRES_PORT", "5432"),
|
||||||
|
"CONN_MAX_AGE": 0,
|
||||||
|
"OPTIONS": {"sslmode": env("POSTGRES_SSLMODE", "require")},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"""生产环境配置:关键安全参数和 PostgreSQL 凭据均必须显式注入。"""
|
||||||
|
|
||||||
|
from .base import env, env_bool, env_list
|
||||||
|
from .base import * # noqa: F403
|
||||||
|
|
||||||
|
|
||||||
|
def required_env(name: str) -> str:
|
||||||
|
"""读取生产必填配置,并在应用启动阶段报告缺失项。"""
|
||||||
|
|
||||||
|
value = env(name)
|
||||||
|
if not value:
|
||||||
|
raise RuntimeError(f"生产环境缺少必填环境变量:{name}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
SECRET_KEY = required_env("DJANGO_SECRET_KEY")
|
||||||
|
DEBUG = False
|
||||||
|
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS")
|
||||||
|
if not ALLOWED_HOSTS:
|
||||||
|
raise RuntimeError("生产环境必须配置 DJANGO_ALLOWED_HOSTS。")
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"NAME": required_env("POSTGRES_DB"),
|
||||||
|
"USER": required_env("POSTGRES_USER"),
|
||||||
|
"PASSWORD": required_env("POSTGRES_PASSWORD"),
|
||||||
|
"HOST": required_env("POSTGRES_HOST"),
|
||||||
|
"PORT": env("POSTGRES_PORT", "5432"),
|
||||||
|
"CONN_MAX_AGE": int(env("POSTGRES_CONN_MAX_AGE", "60")),
|
||||||
|
"CONN_HEALTH_CHECKS": True,
|
||||||
|
"OPTIONS": {"sslmode": env("POSTGRES_SSLMODE", "require")},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = env_list("DJANGO_CSRF_TRUSTED_ORIGINS")
|
||||||
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
SECURE_SSL_REDIRECT = env_bool("DJANGO_SECURE_SSL_REDIRECT", default=True)
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
|
CSRF_COOKIE_HTTPONLY = True
|
||||||
|
SECURE_HSTS_SECONDS = int(env("DJANGO_SECURE_HSTS_SECONDS", "31536000"))
|
||||||
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||||
|
SECURE_HSTS_PRELOAD = env_bool("DJANGO_SECURE_HSTS_PRELOAD", default=False)
|
||||||
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||||
|
X_FRAME_OPTIONS = "DENY"
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"""自动化测试配置:始终使用独立的内存 SQLite 数据库。"""
|
||||||
|
|
||||||
|
from .base import BASE_DIR
|
||||||
|
from .base import * # noqa: F403
|
||||||
|
|
||||||
|
|
||||||
|
SECRET_KEY = "test-only-secret-key-not-for-production"
|
||||||
|
DEBUG = False
|
||||||
|
ALLOWED_HOSTS = ["testserver", "localhost"]
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": ":memory:",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试环境使用快速哈希器;这里只影响测试生成的数据,不改变生产密码策略。
|
||||||
|
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||||
|
MEDIA_ROOT = BASE_DIR / ".test-media"
|
||||||
|
|
||||||
+2
-1
@@ -11,6 +11,7 @@ import os
|
|||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'JobRadar.settings')
|
# 本地直接启动时使用开发配置;部署环境应在进程配置中显式覆盖。
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "JobRadar.settings.development")
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ scope: fullstack
|
|||||||
|
|
||||||
# 开发计划:第一阶段工程基线(新功能)
|
# 开发计划:第一阶段工程基线(新功能)
|
||||||
|
|
||||||
|
> 执行状态:已于 2026-09-01 开始实施;步骤 1“配置与依赖基线”已完成代码和配置落盘,下一步为步骤 2“用户认证与数据归属设计”。依赖环境同步曾因网络下载长时间无进度而中断,恢复网络后需重新执行环境更新和 Ruff/pytest 验证。
|
||||||
|
|
||||||
## 任务概述
|
## 任务概述
|
||||||
|
|
||||||
第一阶段将当前 Django 基础骨架升级为可持续开发的应用基线。本阶段完成分环境配置、密钥隔离、用户认证、用户资料、用户数据归属边界、Agent 运行记录、最小管理及用户页面、日志、测试和代码质量工具。
|
第一阶段将当前 Django 基础骨架升级为可持续开发的应用基线。本阶段完成分环境配置、密钥隔离、用户认证、用户资料、用户数据归属边界、Agent 运行记录、最小管理及用户页面、日志、测试和代码质量工具。
|
||||||
@@ -184,3 +186,4 @@ ruff check .
|
|||||||
| 日期 | 版本 | 说明 |
|
| 日期 | 版本 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 2026-09-01 | 初稿 | 建立第一阶段工程基线开发计划,确认 SQLite 优先、PostgreSQL 集成验证和生产强制 PostgreSQL 的数据库策略。 |
|
| 2026-09-01 | 初稿 | 建立第一阶段工程基线开发计划,确认 SQLite 优先、PostgreSQL 集成验证和生产强制 PostgreSQL 的数据库策略。 |
|
||||||
|
| 2026-09-01 | 步骤 1 | 完成分环境配置、密钥隔离、SQLite/PostgreSQL 配置边界、依赖定义及质量工具配置;环境依赖同步因网络中断待补跑。 |
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
目标:建立适合公网运行和持续开发的Django基础。
|
目标:建立适合公网运行和持续开发的Django基础。
|
||||||
|
|
||||||
- [ ] 拆分开发、测试和生产配置。
|
- [x] 拆分开发、测试和生产配置。
|
||||||
- [ ] 将密钥及本地配置移出版本库。
|
- [x] 将密钥及本地配置移出版本库。
|
||||||
- [ ] 接入Django用户认证和用户资料模型。
|
- [ ] 接入Django用户认证和用户资料模型。
|
||||||
- [ ] 为用户私有业务数据建立统一归属边界。
|
- [ ] 为用户私有业务数据建立统一归属边界。
|
||||||
- [ ] 开发与测试默认使用SQLite,并建立可切换PostgreSQL的迁移基线。
|
- [ ] 开发与测试默认使用SQLite,并建立可切换PostgreSQL的迁移基线。
|
||||||
|
|||||||
+8
-2
@@ -6,7 +6,13 @@ dependencies:
|
|||||||
- django=6.0.8
|
- django=6.0.8
|
||||||
- pip
|
- pip
|
||||||
- pip:
|
- pip:
|
||||||
# 页面采集和 PostgreSQL 驱动已存在于当前开发环境,先纳入可复现环境定义。
|
# 运行时依赖。PostgreSQL 使用 Psycopg 3,避免新项目继续依赖旧版 psycopg2。
|
||||||
- playwright==1.60.0
|
- playwright==1.60.0
|
||||||
- psycopg2-binary==2.9.12
|
- psycopg[binary]==3.3.5
|
||||||
- pillow==12.2.0
|
- pillow==12.2.0
|
||||||
|
- openai-agents==0.22.0
|
||||||
|
# 测试与代码质量依赖统一固定版本,保证本地和持续集成结果一致。
|
||||||
|
- pytest==9.1.1
|
||||||
|
- pytest-django==4.14.0
|
||||||
|
- coverage==7.15.4
|
||||||
|
- ruff==0.16.4
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import sys
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run administrative tasks."""
|
"""Run administrative tasks."""
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'JobRadar.settings')
|
# 命令行默认使用本地开发配置;其他环境必须显式覆盖该变量。
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "JobRadar.settings.development")
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
@@ -18,5 +19,5 @@ def main():
|
|||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[tool.pytest.ini_options]
|
||||||
|
DJANGO_SETTINGS_MODULE = "JobRadar.settings.test"
|
||||||
|
python_files = ["test_*.py", "tests.py"]
|
||||||
|
testpaths = ["."]
|
||||||
|
addopts = "--strict-markers --tb=short"
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
branch = true
|
||||||
|
source = ["."]
|
||||||
|
omit = ["*/migrations/*", "*/tests/*", "manage.py"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
show_missing = true
|
||||||
|
skip_covered = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py313"
|
||||||
|
line-length = 100
|
||||||
|
exclude = ["*/migrations/*"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "B", "UP", "DJ"]
|
||||||
|
ignore = [
|
||||||
|
# Django settings 使用通配导入组合环境配置,变量由框架按名称读取。
|
||||||
|
"F403",
|
||||||
|
"F405",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
known-first-party = ["JobRadar"]
|
||||||
Reference in New Issue
Block a user