Django 从入门到精通:知行博客实战(6.1) · 第 4 / 36 节

创建项目和第一个应用

吴秀峰 17 次阅读 0 条评论 ↓

学习目标

建立 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