创建项目和第一个应用
学习目标
建立 config 配置包和 blog、accounts 两个应用,启动首页,知道每个关键文件负责什么。所有命令均在第 3 章的 zhixing-blog 目录执行。
为什么区分项目与应用
项目描述整个站点如何组合、连接数据库和处理请求;应用描述一个业务领域。config 属于项目,blog 负责文章,accounts 负责用户。本站的 apps/blog、apps/user、apps/course 是同样的分工,只是应用集中放在 apps/ 目录。
Django 默认生成很多说明注释,本章给出精简但完整的配置。先理解路径、注册应用和模板目录,不必记住每个配置项。BASE_DIR 以文件位置计算路径,避免从不同目录启动时读取错数据库。
具体怎么做
在已激活虚拟环境、包含 requirements.txt 的目录中运行:
python -m django startproject config .
python manage.py startapp blog
python manage.py startapp accounts
末尾的点表示把项目入口 manage.py 放在当前目录。不要再创建一层同名目录。然后按照下方完整文件替换生成内容;缺少的目录自行新建,__init__.py 留空。
此时暂不运行 migrate 或 createsuperuser。第 8 章先设置自定义用户模型,第 10 章再执行首次迁移,避免已经建立默认用户表后又更换模型。
本章新增或替换的完整文件
以下路径均相对于练习项目根目录。替换同名文件的全部内容;未列出的文件保持上一章版本。
accounts/__init__.py
创建空文件,用于声明 Python 包。
accounts/apps.py
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
accounts/migrations/__init__.py
创建空文件,用于声明 Python 包。
accounts/models.py
from django.db import models
blog/__init__.py
创建空文件,用于声明 Python 包。
blog/apps.py
from django.apps import AppConfig
class BlogConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "blog"
blog/migrations/__init__.py
创建空文件,用于声明 Python 包。
blog/models.py
from django.db import models
blog/urls.py
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [path("", views.home, name="home")]
blog/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse("知行博客:从写下第一行代码开始。")
config/__init__.py
创建空文件,用于声明 Python 包。
config/asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()
config/settings.py
import os
from pathlib import Path
from django.core.exceptions import ImproperlyConfigured
BASE_DIR = Path(__file__).resolve().parent.parent
DEBUG = os.environ.get("DJANGO_DEBUG", "1") == "1"
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "development-only-never-use-this-key-in-production")
if not DEBUG and (SECRET_KEY.startswith("development-") or len(SECRET_KEY) < 50):
raise ImproperlyConfigured("生产环境必须设置至少 50 字符的独立 DJANGO_SECRET_KEY")
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1,[::1]").split(",")
INSTALLED_APPS = [
"django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles",
"accounts.apps.AccountsConfig", "blog.apps.BlogConfig",
]
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 = "config.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 = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3"}}
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/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
MAILERS = {"default": {"BACKEND": "django.core.mail.backends.console.EmailBackend"}}
config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [path("admin/", admin.site.urls), path("", include("blog.urls"))]
config/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
manage.py
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
读懂启动链路
manage.py 为管理命令设置默认配置;config/settings.py 注册中间件、模板引擎、数据库和应用;config/urls.py 把首页请求交给 blog/urls.py;后者调用 blog/views.py 中的 home()。WSGI 和 ASGI 入口供服务器加载,后面再讲它们的区别。
配置中的开发密钥只是本地示例。DEBUG=0 时会拒绝使用该密钥,生产密钥从环境变量读取。此处的 os.environ 不会自动读取 .env 文件;本地要在终端设置环境变量,Docker Compose 会在部署章节负责注入。
如何确认成功
python manage.py check
python manage.py runserver 127.0.0.1:8000
访问 http://127.0.0.1:8000/,预期看到“知行博客:从写下第一行代码开始。”终端可能提示尚未迁移数据库,这时先只访问不读取数据库的首页。按 Ctrl+C 停止服务。
练习:修改首页的一句话,保存并刷新,观察开发服务器自动重载;再说明哪个文件决定显示文字,哪个文件决定访问地址。
常见问题
No module named config? 检查目录层级,manage.py 和 config/ 应同级。端口占用? 用 runserver 127.0.0.1:8003,浏览器也改用 8003。可以把 runserver 暴露到公网吗? 它用于开发和调试;生产部署将在第 31–32 章使用 Gunicorn 与 Nginx。
官方参考
登录后可保存阅读位置、标记章节完成,并在其他设备继续学习。
登录并保存学习进度 →联系站长
微信:VIPdjango。技术问题建议先在 社区讨论 中交流,方便更多人参与和查阅。
章节讨论 0