Django项目从零搭建:目录结构、配置与数据库迁移实战
发布时间:2026/10/16 5:29:11 锦皓数字建站

1. Django项目结构剖析与准备工作1.1 为什么先谈项目结构而不是直接写代码很多初学者接触Django第一反应是赶紧写个视图、跑起来看看效果。我在带新人或者自己折腾新项目的时候反而会先花十分钟把Django自动生成的目录结构看一遍。原因很简单Django框架和Flask这类微框架的思维方式差异极大Flask允许你从一个文件开始慢慢长成项目但Django从第一秒钟就强迫你按照它的规矩来组织代码。这种强迫其实是一种工程化的保护。我见过太多项目初期图省事把逻辑全堆在views.py里到后期维护时痛苦不堪。Django默认生成的目录结构本身就是官方对最佳实践的固化——models管数据、views管业务、urls管路由、templates管页面、static管静态资源。你顺着这套结构走项目至少在分层上是健康的。在你动手安装任何东西之前先确认Python环境。我建议使用Python 3.10以上版本虽然Django 4.x和5.x都支持3.8但新版本的长字符串支持和类型提示能力能让开发体验好很多。这里有一个小建议永远不要让系统全局Python环境里安装Django而是为每个项目创建独立的虚拟环境。原因也简单不同项目可能依赖不同版本的Django全局安装会导致版本冲突而且升级系统级包时容易破坏现有项目的运行环境。用venv创建虚拟环境并激活然后安装Django整个过程在三分钟内可以完成。python -m venv myenv source myenv/bin/activate # Windows下为 myenv\Scripts\activate pip install django安装完成后可以用一行命令验证版本python -m django --version提示如果你看到类似command not found: django-admin的报错多半是虚拟环境没有激活或者pip安装路径与当前Python解释器不匹配。优先检查虚拟环境状态。1.2 Django自动生成的目录到底在说什么当你在项目目录下执行创建命令Django会生成一套标准的项目骨架。我用一个具体的例子来说明假设项目名为myprojectdjango-admin startproject myproject执行后会出现如下结构myproject/ ├── manage.py └── myproject/ ├── __init__.py ├── settings.py ├── urls.py ├── asgi.py └── wsgi.py这里需要逐个理解。最外层的myproject是项目容器你可以理解为存放整个项目的抽屉。内层的myproject是Python包也就是Django项目的配置核心所有全局配置都在这里。manage.py是项目管理工具它承担了Django大部分命令行操作入口启动开发服务器、执行数据库迁移、创建应用、进入交互式Shell等。实际开发中你与manage.py打交道的时间会非常多。settings.py是全局配置文件涵盖数据库连接、应用注册、中间件、模板引擎、静态文件路径、时区语言等所有配置项。urls.py是根路由配置负责将用户请求分发到各个应用。wsgi.py和asgi.py分别是同步和异步网关接口的入口用于部署场景日常开发时基本不用动它们。很多初学者到这一步就开始困惑为什么创建的是项目而不是应用这里必须先理清Django中项目与应用的边界。项目是整体的站点应用是项目中的功能模块。一个项目可以有多个应用比如一个博客系统项目里可能有文章应用、用户应用、评论应用。一个应用也可以被多个项目复用。在创建一个Django项目时实际上只是搭好了空壳子真正的业务功能由后续创建的apps承载。2. 从创建项目到第一次成功运行2.1 创建项目后必须做的三件事项目骨架生成之后不要急着写代码。我习惯按照固定顺序调整三个地方避免后面反复横跳。第一件事是修改语言和时区配置。Django默认的语言是英语时区是UTC。如果你做的是中文站点必须改settings.py里的两个参数。这个修改的直接效果就是后台管理页面的语言变成中文同时时间记录会按照东八区显示。LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai第二件事是把应用目录创建好。这里的应用是Django中的核心概念一个项目由多个应用组成通过manage.py创建python manage.py startapp blog执行后会生成一个新的目录里面有models.py、views.py、admin.py、apps.py、tests.py、migrations/等文件。我的习惯是在创建应用的瞬间就把它注册到settings.py的INSTALLED_APPS里这样可以避免后续忘记注册导致Django不识别这个应用。第三件事是配置DEBUG与ALLOWED_HOSTS。开发阶段DEBUG保持默认的True方便看到详细报错信息。ALLOWED_HOSTS在开发阶段可以设置为空列表但当你用手机访问开发服务器或部署到云服务器时就必须填入对应的域名或IP地址否则Django会直接拒绝请求。我在本地测试时常用的一段配置是这样的DEBUG True ALLOWED_HOSTS [localhost, 127.0.0.1, 192.168.1.100] # 根据实际局域网IP调整2.2 第一次启动runserver时会发生什么完成上述修改后在项目根目录执行启动命令python manage.py runserver正常情况下终端会输出一条类似Starting development server at http://127.0.0.1:8000/的信息然后在浏览器打开对应地址能看到一个默认的Django欢迎页。这个页面在Django新版里是带有火箭图标的引导页意味着框架已经正常运转。这里我要特别说明一下第一次点击runserver时Django会提示你执行数据库迁移。因为这个欢迎页本身不依赖数据库所以不迁移也能看到。但如果你直接访问默认的管理后台就会遇到数据库表不存在的错误。这涉及数据库配置具体内容我会放到后面专门讲。我自己在第一次启动项目时还会顺手观察终端输出的请求日志。每当你刷新页面或发起一个请求终端都会打印一条日志包括请求方法、路径、状态码和耗时。这个日志在开发阶段排查问题时非常有用。很多新手遇到页面白屏或报错时第一反应是到处找页面源码其实第一时间应该看终端日志错误信息往往已经真实记录在那里了。runserver还有一个很有价值的特性自动重载。当你修改Python代码并保存文件开发服务器会自动重启无需手动操作。对于模板文件的修改甚至不需要服务器重启就能生效。但有两个例外需要记住新增文件时通常会自动重载但修改settings.py中某些特定配置例如INSTALLED_APPS时有时需要手动重启才能完全生效。还有一个经常被忽略的细节runserver默认的监听地址是127.0.0.1:8000也就是只能本机访问。如果你想让同一局域网内的其他设备访问需要指定监听地址python manage.py runserver 0.0.0.0:8000注意这里0.0.0.0代表监听所有网络接口。执行前请确认ALLOWED_HOSTS已包含目标IP否则访问时依然会被拒绝。3. settings.py配置深度拆解3.1 INSTALLED_APPS和MIDDLEWARE这些名字背后是什么settings.py里最劝退新手的就是一大堆英文配置项但拆开看其实逻辑非常清楚。我用生活类比来说明Django运行起来就像一家餐厅INSTALLED_APPS是餐厅入驻的各个档口MIDDLEWARE是门口的安检和引导员TEMPLATES是后厨的菜谱模板DATABASES是仓库系统。INSTALLED_APPS默认包含七个内置应用每个都有独立职责。django.contrib.admin是后台管理界面django.contrib.auth是用户认证体系django.contrib.contenttypes是内容类型框架django.contrib.sessions是会话管理django.contrib.messages是消息提示django.contrib.staticfiles是静态文件处理。简单说它们是Django自带的基础设施能覆盖大部分Web应用的通用需求。当你创建了自己的应用并注册之后Django才会去加载这个应用里的models、admin、views等模块。不留心这一点最常见的现象就是你明明写了models.py执行makemigrations却提示No changes detected大概率就是因为应用没有注册进INSTALLED_APPS。MIDDLEWARE是一个处理请求/响应的管道层。每个请求从进入Django到离开Django会依次经过这里的每个中间件。比如SessionMiddleware负责读取会话信息CsrfViewMiddleware负责检查CSRF令牌AuthenticationMiddleware负责绑定当前登录用户。中间件有严格的顺序要求默认顺序不要随意改动。我见过有人因为调整顺序导致CSRF验证一直失败最后才发现是中间件位置不对。3.2 DATABASES配置不用SQLite时要注意什么Django默认使用SQLite数据库。对于学习和原型开发SQLite完全够用它把整个数据库存储在一个文件里不需要单独的数据库服务零配置就能跑起来。很多人用Django做正式项目时会切换到MySQL或PostgreSQL。切换数据库的核心是修改DATABASES字典。MySQL的典型配置示例DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: mydb, USER: myuser, PASSWORD: mypassword, HOST: 127.0.0.1, PORT: 3306, } }这里需要提醒的是使用MySQL时除了配置DATABASES还必须安装对应的数据库驱动。比较常见的是安装mysqlclient库或者使用pymysql并在项目初始化文件里写一段兼容代码。我个人的建议是能装mysqlclient就优先它性能更好踩坑更少。无论使用哪种数据库切换后的第一件事永远是执行数据库迁移让Django内置应用的数据表在目标数据库里创建出来python manage.py migrate迁移的本质是Django通过ORM把你应用中的模型转换为建表语句并同步到数据库。如果是新项目直接migrate就会创建一组初始表包含用户、权限、会话等内容。这是在后续创建你自己的数据模型之前必须完成的一步否则后台管理登录时就会因为缺少数据表而报错。3.3 模板与静态文件配置的默认逻辑TEMPLATES配置项里有一个DIRS列表默认是空的。它用于指定Django额外查找模板文件的目录。每个应用内部都可以有自己的templates目录Django会自动去这些目录查找模板。但如果你有全局共用模板的需求比如整个站点共用的首页、基础布局就需要配置DIRS。我常用的方式是TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [BASE_DIR / templates], 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, ], }, }, ]这里的BASE_DIR / templates意思是项目根目录下的templates文件夹这是Django 3.1以上版本的写法利用pathlib让路径拼接更加直观。我个人很推荐这个写法它省去了记忆各种join函数的麻烦。静态文件CSS、JS、图片的配置涉及两个点STATIC_URL是浏览器访问静态文件时的URL前缀STATICFILES_DIRS是项目中额外存放静态文件的目录列表。每个应用内的static目录是自动被识别的但整个项目级别的公共静态文件需要在STATICFILES_DIRS里手动指定。我在新项目里习惯把所有全局静态文件放在根目录的static文件夹下配置如下STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static]这里有个容易踩的坑STATICFILES_DIRS与STATIC_ROOT不要指向同一个目录。STATIC_ROOT是部署阶段收集所有静态文件的地方通常由collectstatic命令自动填充开发阶段不用特意创建。4. 数据库迁移与超级用户创建4.1 从模型到数据表的完整链路在Django里你不需要手写SQL建表而是通过编写Python类即模型来定义数据结构。模型定义在应用的models.py中每个类对应一张数据表每个类属性对应一个字段。用一个简单的文章模型来说from django.db import models class Article(models.Model): title models.CharField(max_length200) content models.TextField() created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return self.title这里最关键的一点是理解makemigrations与migrate的区别。makemigrations的作用是比对当前模型定义与上一次迁移记录之间的差异生成一个迁移文件存放在应用的migrations目录下。这个文件本质上是描述数据表变化的版本记录不直接操作数据库。执行migrate才会把迁移文件中记录的操作真正应用到数据库创建或修改数据表。这个两步走的设计是有道理的。首先迁移文件可以被版本控制系统追踪方便团队成员看到数据结构的变更历史。其次你在开发机上生成迁移文件后可以review一遍再执行避免模型写错导致直接污染数据库。具体操作顺序如下python manage.py makemigrations blog python manage.py migrate第一条命令末尾的blog是应用名可以指定只对某个应用生成迁移。执行完第一条命令后查看一下migrations目录下新生成的迁移文件你会看到里面用Python代码描述的表结构比如字段类型、是否为空、默认值等。理解迁移文件的内容对后续排查数据库问题非常有帮助。4.2 忘记超级用户怎么办当你准备进入Django自带的管理后台时必须先创建一个管理员账号。这一步在命令行完成python manage.py createsuperuser执行后按提示输入用户名、邮箱、密码即可。这里有个小细节在输入密码时终端不会显示任何字符连星号也没有这是正常的安全行为别以为自己没输入进去。另外Django默认会做密码强度校验如果设置的密码太简单系统会提示你确认或重试。开发环境可以强制用简单密码但生产环境强烈建议使用强密码因为管理后台一旦暴露在公网弱密码基本等于给人开后门。创建完超级用户后访问路径是http://127.0.0.1:8000/admin/用刚创建的账号登录就能看到Django管理后台。此时后台里能看到用户、组等内置模型但你在第一步定义的Article模型并不会自动出现。要让它出现在管理后台需要在应用的admin.py里注册from django.contrib import admin from .models import Article admin.site.register(Article)这个操作的原理很简单Django内置的管理后台是一个通用CRUD工具它不知道你的自定义模型长什么样需要你显式告诉它把这个模型纳管。注册之后刷新后台页面就可以在界面上直接增删改查文章数据了。5. 第一个应用的完整业务闭环5.1 编写视图与路由请求是怎么被接住的到了这一步项目基本框架已经跑通现在要开始让项目真正响应业务请求。Django处理一个请求的过程可以用下面的链路概述浏览器发起URL请求 - Django的URLconf根据路径匹配对应路由 - 执行绑定的视图函数 - 视图函数返回HTTP响应。首先要写视图函数。回到blog应用的views.py一个最基础的视图如下from django.http import HttpResponse from .models import Article def article_list(request): articles Article.objects.all().order_by(-created_at) text for article in articles: text f{article.title}\n return HttpResponse(text, content_typetext/plain; charsetutf-8)这个视图做了三件事从数据库取所有文章按创建时间倒序排列将标题拼接成普通文本返回。虽然简陋但已经完整走通了数据库到页面的流程。接下来配置路由。Django的路由配置分为两层根路由和应用路由。根路由在项目目录的urls.py里负责将不同路径前缀的路由分发给对应应用。这种做法在项目变大后优势特别明显每个应用只管自己内部的URL规则不用在根路由里堆一大堆路径。先在blog应用里创建一个urls.py文件from django.urls import path from . import views urlpatterns [ path(articles/, views.article_list, namearticle_list), ]然后在项目根目录的urls.py中把应用路由include进来from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(blog.urls)), ]完成之后访问http://127.0.0.1:8000/articles/就能看到文章标题列表了。5.2 通用视图与模板渲染的优雅改造直接返回HttpResponse字符串的方式不适合真实开发因为页面结构通常包含复杂的HTML、CSS和交互。Django提倡使用模板系统来渲染页面同时在应对特定业务模式时使用通用视图可以减少大量重复代码。以文章列表为例用ListView改造上面的代码from django.views.generic import ListView from .models import Article class ArticleListView(ListView): model Article template_name blog/article_list.html context_object_name articles ordering [-created_at]这段代码的意思是查询Article模型的所有记录渲染template_name指定的HTML模板在模板中通过articles这个变量名访问数据。模板文件内容大致如下!DOCTYPE html html headtitle文章列表/title/head body ul {% for article in articles %} li{{ article.title }}/li {% endfor %} /ul /body /html使用通用视图的收益是巨大的代码量更少、职责清晰、可读性更好。而且Django内置了大量通用视图比如DetailView用于详情页CreateView用于新增UpdateView用于编辑DeleteView用于删除它们之间还能通过权限系统集成用户认证。我还想强调模板中{{ }}和{% %}的区别。{{ }}用于输出变量{% %}用于执行逻辑比如循环、条件判断、模板继承等。初学者常混淆两者导致模板渲染结果不符合预期。记住一个简单的判断标准内容要显示在页面上用双花括号内容要控制页面的渲染方式用单花括号加百分号。5.3 配置完成后的一次完整启动流程经过以上步骤现在把整个启动流程串起来走一遍。我以一个新项目从零到正常运行的完整命令序列为例# 1. 创建项目与应用 django-admin startproject myproject cd myproject python manage.py startapp blog # 2. 在settings.py中注册blog应用手动修改 # 3. 修改语言、时区、数据库配置按需 # 4. 首次数据库迁移 python manage.py makemigrations blog python manage.py migrate # 5. 创建超级用户 python manage.py createsuperuser # 6. 启动开发服务器 python manage.py runserver每一步的目的都对应着前面某个章节的内容。这套流程就是Django开发中最经典的基础启动路径。你把每一步的意图理解透彻了后续面对复杂项目时也能清晰地知道自己处于哪个环节需要调整什么配置。6. 开发中最常见的几个误区与排查经验6.1 更改配置后没有生效问题出在哪我在实际经验中发现不少Django新手配置改完之后报错或者不生效第一反应往往是怀疑自己代码写错了。实际上很多配置类的问题都源于服务器没有真正重启或者缓存没有清理。比如修改了settings.py里的INSTALLED_APPS注册了一个新应用但访问时提示应用不存在。这种情况最可能是开发服务器还停留在旧状态虽然runserver有自动重载机制但在某些编辑器环境下文件监听可能失效。手动在终端按下CtrlC停止服务再重新runserver一次通常是最高效的解决办法。再比如修改了模板文件但页面展示的还是旧版本。虽然模板修改不需要重启服务器但Django的模板缓存机制在DEBUGFalse时会在生产环境缓存模板内容。如果你在开发阶段就手动设置了DebugFalse来模拟生产环境就很可能遇到这个坑。建议开发阶段始终让DEBUG保持True不要提前模拟生产配置。还有一个非常隐蔽的问题改了URL配置但访问还是404。可能原因是你的浏览器缓存了旧路由或者Django的URL路由匹配是自上而下的前面的正则或者路径规则匹配到了同一个URL导致后面的路由永远不被触发。排查这类问题一个很有效的做法是直接在浏览器地址栏加上一个查询参数比如?t123绕过浏览器缓存再观察终端日志里的请求路径。6.2 迁移时报错的三个高频原因数据库迁移是Django开发中遇到报错最多的一环。常见情况可以归纳成三类。第一类是迁移文件与数据库状态不一致。典型场景是你手动删除了某个迁移文件但数据库的django_migrations表里还记录着这条迁移。执行migrate时会提示table already exists或者relation already exists。解决方法是不要手动删除迁移文件而是用python manage.py migrate 应用名 迁移文件名去回滚到指定版本再用python manage.py makemigrations重新生成。第二类是模型字段类型与数据库不兼容。比如在SQLite里一切安好切到MySQL时报出某个字段类型错误。解决办法是在模型定义中明确指定字段参数比如CharField必须设置max_length当模型字段还没来得及设置max_length时makemigrations会直接报错。第三类是外键或关联关系的迁移顺序问题。多个应用之间存在外键关联时如果A应用迁移时引用了B应用还没创建的表migrate就会失败。这时候可以指定迁移顺序或者在定义外键时使用on_deletemodels.CASCADE等参数并确保依赖应用先完成迁移。6.3 如何用一个最小的测试站点验证所有配置配置了这么多内容怎么快速判断项目环境是否一切正常我常用一个三板斧验证方案整个验证过程在两分钟内完成。第一步启动runserver访问根路径。如果能看到默认欢迎页说明项目本身没有致命错误URLconf可以正常工作。第二步在root路径临时绑定一个视图返回一个简单的HTTP响应验证应用的路由和视图是否连接成功。比如在项目的urls.py里加入一个直接调用HttpResponse的路径。第三步创建超级用户并访问admin后台。如果可以正常登录且看到管理界面说明数据库迁移正确、认证系统正常、模板引擎正常。这三步走完理论上你的Django开发环境已经是完整可用的状态了。我自己在创建一个新项目时这套验证流程是固定的。它能让我在添加任何业务代码之前先确认地基是牢固的。很多时候项目出问题根源不在业务代码而在一开始的配置环节就有隐患。7. 一些值得养成的Django开发习惯如果前面六个章节你都能完整消化那么Django的基本运行逻辑已经有清晰认知了。最后分享几个我在长期开发中积攒下来的习惯这些不一定写在官方文档里但真的能帮你少走弯路。第一个习惯是小步提交。每次只做一件事写一个模型就生成一次迁移写完一个视图就启动服务器测试一次。不要一次性写大量代码再统一测试因为出问题时你根本不知道是哪个环节出错了。这个习惯配合Django的自动重载特性开发效率会高出许多。第二个习惯是全部错误先看终端。前端页面报错时新手容易直接翻浏览器开发者工具但Django的请求日志会直接告诉你哪个视图、哪个模板、哪一行出了什么异常。大部分后端错误在终端日志中都有明确线索先看终端能节省大量时间。第三个习惯是测试数据与真实数据分离。我通常在开发环境使用与生产环境不同的数据库实例并定时清理测试脏数据。这样可以避免测试数据污染真实业务数据也能避免因为删库或者改库操作导致无法挽回的损失。第四个习惯是代码提交前把settings.py里敏感信息隐藏。尤其是数据库密码、SECRET_KEY等操作信息。比较通用的做法是把这些敏感配置写进环境变量或者在项目根目录放一个.env文件用Python的os.environ读取。这对于后面部署上线尤其重要我在实际项目中见过不止一次因为SECRET_KEY泄露导致的线上安全问题。Django的学习曲线在Web框架里不算低但它的工程化设计一旦上手带来的收益远超前期的学习成本。从配置到运行的每一步都是这套框架深思熟虑后的结果。你把这个过程理解通了再往里加业务功能心里就有底了。在后续的系列文章里我会继续深入Django的ORM模型、表单处理、用户认证、REST API等核心主题每一个都可以单独成篇。今天这篇相当于给你一个能跑的起点你照着操作一遍之后最要紧的事情就是动手改一改、试一试——改坏了大不了重新来改明白了也就真正学会了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。