简介:这是一份基于Python的简单Django Web项目源码,是一个定位清晰的Web开发入门级学习工程,尤其适合刚接触Django框架、想快速得到一个可运行网站基础功能的开发者;对于正在完成课程设计或毕业设计的学生,也可以直接作为基础模块进行参考和二次开发。项目围绕用户登录与页面渲染展开,通过实际可运行的代码展示了配置文件、URL路由映射、视图函数、数据模型和HTML模板之间的标准协作方式,帮助初学者直观理解一次请求从浏览器到达服务器、经视图逻辑处理后返回页面的完整链路。资源以zip格式打包,共40个文件,主要包含13个Python源码文件、24个Python编译缓存文件、1个HTML页面模板、1个SQLite数据库文件和1份说明文档,整体占用空间仅155KB,文件精简而结构完整,下载后可快速在本地Python环境中打开查看。目前已有480人学习或下载。通过仔细阅读源码,读者可以掌握Django工程初始化、ORM模型定义、后台管理配置、路由编写、模板渲染等关键技能;同时还可以直接运行项目,尝试调整登录校验规则、修改页面样式或新增数据库字段,在动手调试中建立对框架各层协作机制的直观印象,为独立开发更完整的Web应用打下良好基础。
1. 从一个空目录到能访问的Django页面:这个项目源码到底在解决什么
如果你搜到这个标题,大概率不是想听Django有多强大,而是手头有个课程设计、毕业设计或者公司内部小工具的需求,需要一个“能跑、能改、能交差”的起点。所谓“基于Python的简单Django Web项目设计源码”,说白了就是一套用Django搭起来的最小但完整的Web应用骨架:有页面、有数据库、有表单提交、有后台管理,代码量控制在几百行以内,新手拿到后能看懂每个文件在干什么,改一改就能变成自己的项目。
我用这个思路做过不少类似的东西,包括给某高校实训课写的课程管理Demo,给某公司内部做的会议室预订小工具。它们的共同点是:不需要高并发、不需要分布式,核心诉求是“一周内上线、三个月内有人用、后续能维护”。这篇文章把我验证过的目录结构、参数配置和踩过的坑完整写出来,你照着做,从零到一个能访问的Django应用,大概需要一个小时。
2. 搭好Django最小可运行环境:从解释器版本到第一个“它活了”的请求
2.1 为什么“简单Django项目”也要从venv开始
很多新手图省事,直接用全局Python环境装Django,装完发现系统里其他脚本跑不了了,或者Django版本和项目要求对不上。我习惯的做法是每个项目一个虚拟环境,这是后面所有操作不出乱子的前提。
# Python 3.8 及以上版本都自带venv模块 python3 -m venv venv # 激活虚拟环境(Windows下执行 venv\Scripts\activate) source venv/bin/activate # 安装Django,这里固定大版本,避免未来升级导致行为变化 pip install "django>=4.2,<5.0"激活后命令行前面会出现(venv)前缀,这说明你已经在隔离环境里。安装完成后用python -m django --version确认版本,我一般要求4.2 LTS版本,它支持到2026年,比追最新版稳得多。
参数说明:venv是虚拟环境目录名,可以随便起,但建议用venv,因为.gitignore里通常默认忽略它。Python版本建议3.9或3.11,3.10有个match语法和某些库的兼容问题,虽然Django本身没坑,但后续你要装其他依赖时3.11更稳。
这里有个常见误区:不要用pip install django直接装最新版。Django每年发两个版本,最新版往往是“尝鲜版”,文档和第三方库的适配还没跟上。锁定>=4.2,<5.0的意思是装4.2系列的最新补丁版,这是生产环境验证过的组合。
2.2 创建项目和App:startproject与startapp的分工逻辑
Django把“项目”和“应用”分成两个概念,这是新手最容易懵的地方。项目是一个网站的全局配置,应用是里面的一个功能模块。一个项目可以挂多个应用,比如一个商城项目里有“用户”“商品”“订单”三个应用。
# 创建项目,名字用纯英文小写 django-admin startproject mysite cd mysite # 创建应用,vote是“投票”功能模块 python manage.py startapp vote执行后你的目录结构是这样:
mysite/ ├── manage.py # 项目管理入口,所有命令都通过它执行 ├── mysite/ # 全局配置目录 │ ├── settings.py # 项目配置文件 │ ├── urls.py # 根路由配置 │ └── wsgi.py # 部署入口 └── vote/ # 应用目录 ├── models.py # 数据模型 ├── views.py # 业务逻辑 ├── urls.py # 应用自己的路由 └── migrations/ # 数据库迁移记录为什么要分这么细?因为Django的哲学是“可插拔”。你以后做第二个项目时,可以直接把vote这个目录拷过去,只要配置好路由就能复用。我见过把全部代码塞进一个views.py的项目,三个月后自己都找不到逻辑在哪。按模块拆分文件,是对未来自己的仁慈。
注意startapp不会自动帮你建urls.py,需要手动创建。这是Django一个容易漏掉的细节,后面路由配置时我还会提。
2.3 让第一个页面跑起来:urls、views、settings三处联动
Django处理一个请求的流程很直接:浏览器发请求到URL路由,路由分发给视图函数,视图函数处理后返回响应。三个文件必须同时配好才能看到页面。
先在vote/views.py里写一个最简单的视图:
from django.http import HttpResponse def index(request): return HttpResponse("欢迎来到投票系统")然后在vote/目录下新建urls.py:
from django.urls import path from . import views # app_name用于模板中反向解析URL,多应用时避免链接写错 app_name = 'vote' urlpatterns = [ # 第一个参数是空字符串,表示匹配该应用下的根路径 path('', views.index, name='index'), ]最后修改全局路由mysite/urls.py,把应用挂上去:
from django.contrib import admin from django.urls import include, path urlpatterns = [ path('admin/', admin.site.urls), # 任何以vote/开头的请求,都交给vote应用的urls.py处理 path('vote/', include('vote.urls')), ]启动开发服务器:
python manage.py runserver浏览器访问http://127.0.0.1:8000/vote/,看到“欢迎来到投票系统”就说明通了。
参数说明:include()的作用是把子路由的匹配责任转交给应用,这样全局路由文件保持干净。name='index'给这个URL起了名字,以后模板里写{% url 'vote:index' %}比硬编码路径更安全——路径改了,模板不用改。这三个文件是Django请求链路的铁三角,不管项目多复杂,本质都是这个结构在重复。
3. 把“简单”做成“完整”:核心模块与源码目录设计的落地写法
3.1 数据模型先行:用投票/待办这类经典模型把ORM跑通
一个Web项目如果只有静态页面,那根本不叫Web项目。Django的价值在于它的ORM(对象关系映射)——你写Python类,它自动帮你生成数据库表。设计模型时记住一个原则:先想清楚业务对象和它们的关系,再动手写代码。
以投票系统为例,两个核心模型:问题(Question)和选项(Choice)。一个问题有多个选项,一个选项属于一个问题,这是典型的一对多关系。
# vote/models.py from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200, verbose_name="问题内容") pub_date = models.DateTimeField(verbose_name="发布日期") def __str__(self): return self.question_text class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name="所属问题") choice_text = models.CharField(max_length=200, verbose_name="选项内容") votes = models.IntegerField(default=0, verbose_name="票数") def __str__(self): return self.choice_text写完模型后,执行两条命令让数据库结构生效:
# 生成迁移文件(相当于记录“我要建这两张表”) python manage.py makemigrations vote # 执行迁移(真正在数据库里建表) python manage.py migrate逻辑说明:makemigrations是Django特有的“版本控制”机制,它把模型变化记录到migrations/目录下,而不是直接改数据库。好处是你可以回滚、可以代码审查、可以多环境同步。migrate则是把迁移记录应用到实际数据库。
参数说明:on_delete=models.CASCADE表示问题被删除时,它的所有选项也跟着删除,这是外键的级联删除行为。如果你不想删选项,可以用SET_NULL(配合null=True)或PROTECT(有选项时禁止删问题)。默认的SQLite数据库文件会在项目根目录自动生成,不需要你手动创建,但注意.gitignore里要加上db.sqlite3,否则提交代码时会把测试数据一起提交。
3.2 视图层选函数还是类:单人维护时怎么选更稳
Django视图有两种写法:函数视图(FBV)和类视图(CBV)。新手看到教程里两种都有,容易纠结。我的建议很直白:项目逻辑简单、你自己维护,就老老实实用函数视图。函数视图的流程是线性的,从上往下读就行;类视图把GET、POST拆成类方法,抽象度更高,适合逻辑复杂的场景,但报错时堆栈追踪会多绕一层。
以投票详情页为例,函数视图长这样:
from django.shortcuts import get_object_or_404, render from .models import Question def detail(request, question_id): # 尝试取问题,不存在时返回404,而不是报500错误 question = get_object_or_404(Question, pk=question_id) return render(request, 'vote/detail.html', {'question': question})对应路由:
# vote/urls.py path('question/<int:question_id>/', views.detail, name='detail'),参数说明:<int:question_id>是Django的路径转换器,它匹配一个整数,并作为参数传给视图函数。这样URL从/vote/question/3/到视图函数的question_id=3,类型转换自动完成。如果是字符串,可以用<str:slug>,但能定成int就别用str,省得在视图里做类型判断。
类视图版本只是换了个写法,效果相同。如果你想省事又想保持可读性,还可以在函数视图上装饰@require_GET或@require_POST,比在函数内部判断request.method干净得多。
3.3 后台管理:注册模型后白拿的增删改查
Django自带一个后台管理界面,这是它比起其他框架最“香”的特性——业务数据的管理后台不用写一行CRUD代码。前提是你在admin.py里注册模型。
# vote/admin.py from django.contrib import admin from .models import Question, Choice # 注册模型,让它们在后台可见 admin.site.register(Question) admin.site.register(Choice)还需要创建一个管理员账号:
python manage.py createsuperuser # 按提示输入用户名、邮箱、密码访问http://127.0.0.1:8000/admin/,登录后就能看到“问题”和“选项”两张表的增删改查界面。
逻辑说明:admin.site.register是最简单的注册方式,适合模型少、字段不复杂的场景。如果你要给字段加搜索、加筛选,可以定义ModelAdmin类:
@admin.register(Question) class QuestionAdmin(admin.ModelAdmin): list_display = ['question_text', 'pub_date'] search_fields = ['question_text'] list_filter = ['pub_date']参数说明:list_display控制后台列表页显示哪些列,search_fields开启搜索框(按字段模糊查询),list_filter在右侧生成日期筛选器。这三个参数是后端管理中最常用的,能覆盖80%的需求。
注意:createsuperuser创建的账号存在数据库里,如果数据库文件被删除(比如你重置了项目),账号也没了,需要重新创建。这是开发期常遇到的情况,不算bug。
3.4 源码文件清单与各文件职责
一个“简单”Django项目的源码文件并不多,但每个文件都有明确职责。新手拿到别人的项目源码,第一步应该是先看文件结构,而不是打开某个代码文件硬读。我习惯的阅读顺序是:先看settings.py了解项目配置,再看根urls.py了解有哪些路由,然后按路由逐个进App看views.py和models.py。
| 文件路径 | 职责 | 新手常犯错误 |
|---|---|---|
manage.py | 所有Django命令的入口 | 不修改,保持默认 |
mysite/settings.py | 全局配置:App列表、数据库、模板、静态文件 | 忘记把新App加入INSTALLED_APPS |
mysite/urls.py | 根路由分发 | 不写include,导致子路由不生效 |
vote/models.py | 定义数据模型 | 模型改了不makemigrations |
vote/views.py | 业务逻辑处理 | 忘记from django.shortcuts import render |
vote/admin.py | 后台管理注册 | 不注册,后台看不到数据表 |
templates/ | HTML模板 | 模板目录配置错误导致模板找不到 |
INSTALLED_APPS这个配置项是Django的“插件列表”,每新建一个App,都必须把它加进去,否则Django不认识这个应用。这个错误排在避坑榜第一位:
# mysite/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 新增这行:你的应用 'vote', ]我见过有人在这个列表里漏掉django.contrib.staticfiles,结果静态文件功能一直报错。系统自带应用尽量全保留,它们之间有依赖关系——django.contrib.admin依赖auth、contenttypes等模块,删除任何一个都可能导致后台崩溃。
4. 模板与静态资源:让页面不是白底黑字的三个关键设置
4.1 模板继承:base.html与block的玩法
新手写Django模板最容易犯的错误是每个页面从头写一个完整的HTML,复制粘贴<head>和导航栏。正确的做法是用模板继承:写一个base.html作为骨架,子页面只填自己特有的内容块。
<!-- templates/base.html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}默认标题{% endblock %}</title> </head> <body> <nav> <a href="{% url 'vote:index' %}">首页</a> </nav> <main> {% block content %} <!-- 子模板将覆盖这里 --> {% endblock %} </main> </body> </html>然后在模板目录下建一个和App同名的子目录,避免不同App间的模板文件重名——Django的模板引擎是多App共享一个目录的,不加命名空间会互相覆盖。这又是新手容易踩的坑。
<!-- templates/vote/detail.html --> {% extends "base.html" %} {% block title %}{{ question.question_text }} - 投票{% endblock %} {% block content %} <h1>{{ question.question_text }}</h1> <form action="{% url 'vote:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <label> <input type="radio" name="choice" value="{{ choice.id }}"> {{ choice.choice_text }} </label><br> {% endfor %} <button type="submit">投票</button> </form> {% endblock %}逻辑说明:{% extends "base.html" %}声明继承,{% block content %}和{% endblock %}定义了一个可覆盖的区域。子模板里同名block的内容会替换父模板的默认内容。没有写在block里的内容不会显示——这是新手最容易疑惑的地方,以为子模板内容会自动拼进去,其实必须显式放在block里。
{{ question.choice_set.all }}是Django ORM的反向查询:一个Question对象可以通过choice_set访问它关联的所有Choice对象。如果你给外键字段加了related_name='choices',那写法就是question.choices.all(),更简洁。
4.2 静态文件按App隔离:css/js/images的目录约定
Django的静态文件(CSS、JS、图片)有一个约定:每个App下建一个static/<app_name>/目录,项目级的静态文件放在根目录static/下。这样Django能自动找到它们,不会撞名。
/* vote/static/vote/style.css */ body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }在模板中加载静态文件:
<!-- 在base.html的head区域加入 --> {% load static %} <link rel="stylesheet" href="{% static 'vote/style.css' %}">对应settings.py配置:
# mysite/settings.py STATIC_URL = '/static/' # 开启DEBUG时,Django自动从各App的static目录寻找静态文件 # 关闭DEBUG后,需要用collectstatic命令收集到一处统一提供 STATIC_ROOT = BASE_DIR / 'staticfiles'参数说明:STATIC_URL是浏览器访问静态文件的URL前缀,一般保持/static/不变。STATIC_ROOT只在部署时有用,python manage.py collectstatic会把所有App和项目里的静态文件复制到这个目录,交给Nginx等服务器统一托管。
我在开发期不会刻意配STATICFILES_DIRS,因为各App的static/目录已经是默认搜索路径。只有当你需要把某个不属于任何App的静态目录(比如项目根目录下的assets/)也纳入管理时,才需要加它。
4.3 表单与CSRF:POST请求必踩的一关
Django的CSRF防护是安全的一大卖点,但也是新手第一次写POST表单时必然撞到的墙——不加{% csrf_token %}就提交,返回403错误。这个机制的原理是:服务器生成一个随机token,放进表单隐藏域,提交时校验。攻击者无法跨域伪造这个token,所以能有效防止跨站请求伪造。
<form action="{% url 'vote:vote' question.id %}" method="post"> {% csrf_token %} <!-- 表单内容 --> </form>对应的视图处理:
from django.shortcuts import get_object_or_404, redirect from django.http import HttpResponseRedirect from django.urls import reverse from .models import Choice, Question def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # 重新渲染详情页,并带一个错误提示 return render(request, 'vote/detail.html', { 'question': question, 'error_message': "你没有选择任何选项。", }) else: selected_choice.votes += 1 selected_choice.save() # POST成功后重定向,避免刷新页面时重复提交 return HttpResponseRedirect(reverse('vote:results', args=(question.id,)))逻辑说明:这是Django官方投票教程的规范化写法,三个细节值得记。第一,处理POST的视图必须用HttpResponseRedirect或redirect返回,而不是直接render,这遵循“POST-重定向-GET”模式,防止用户刷新页面时表单重复提交。第二,request.POST['choice']用下标取值,key不存在会抛KeyError,所以要用try包住。第三,投票成功后在else分支里操作数据库,save()才真正写入。
参数说明:reverse('vote:results', args=(question.id,))是“反向解析URL”——根据路由名称和参数生成完整URL字符串。这样做的好处是如果以后改了URL路径,代码和模板里的链接不用跟着改,统一由路由表管控。坑点是路由里的name必须唯一,多个App里出现同名name会互相覆盖,所以app_name = 'vote'这个命名空间就发挥作用了。
5. Django项目避坑指南:新人最常翻车的5个真实案例
5.1 迁移报错“No changes detected”——模型改了但没生成迁移文件
现象:你改了models.py,加了几个字段,执行python manage.py makemigrations却提示No changes detected。
原因:最常见的是App没有加进INSTALLED_APPS,Django根本不扫描这个应用的模型。另一个可能是你在错误的目录下执行命令——改模型时要先cd到包含manage.py的项目根目录,否则命令找不到项目。
解决:第一步检查settings.py的INSTALLED_APPS里有没有你的App名。第二步确认你当前在manage.py所在目录。如果都是对的,试着执行python manage.py makemigrations 应用名(显式指定App),Django会给出更详细的提示。
5.2 静态文件404——DEBUG=False后图片和样式全丢
现象:开发阶段一切正常,一旦设置DEBUG = False,页面样式全乱、图片显示不出来。
原因:Django只在DEBUG=True时自动托管静态文件(通过runserver)。生产环境下runserver不会提供静态文件,必须用collectstatic收集后交给专门服务器,或者用中间件处理。
解决:如果你只是本地演示,不想配Nginx,最简单的方式是保持DEBUG = True(仅限本地/内网演示),把ALLOWED_HOSTS配好。如果一定要关调试,执行python manage.py collectstatic,确认STATIC_ROOT目录下有文件,再用中间件方式托管:
# 安装whitenoise后,在settings.py的MIDDLEWARE中加入 # 'whitenoise.middleware.WhiteNoiseMiddleware'(放在SecurityMiddleware之后) # 并设置 STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'5.3 表单提交403——CSRF验证失败的三个原因
现象:表单一提交就报CSRF verification failed,或者后台登录页面也报403。
原因:三种情况最常见。一是模板里忘了{% csrf_token %}。二是MIDDLEWARE配置里把CsrfViewMiddleware删了或放错了位置。三是请求头缺少Referer(某些浏览器插件或代理会去掉它)。
解决:第一检查模板,加上{% csrf_token %}。第二检查MIDDLEWARE数组,确保django.middleware.csrf.CsrfViewMiddleware存在。第三如果开发时用了HTTP而非HTTPS,检查浏览器是否设置了“发送严格Referrer”策略,必要时在settings里加CSRF_TRUSTED_ORIGINS。
5.4 时区与时间显示差8小时
现象:在后台发布一条记录,列表页显示的时间和本地时间不一致,差8个小时。
原因:Django默认的TIME_ZONE = 'UTC',数据库存的是UTC时间,页面显示时没做转换。中国用户打开后台,看到的时间比北京时间晚8小时。
解决:把settings.py里的TIME_ZONE改成'Asia/Shanghai',并把USE_TZ保持为True(不建议关掉,Django的时区系统是设计好的,关掉会埋下隐患)。
TIME_ZONE = 'Asia/Shanghai' USE_TZ = True注意:改完后已存在数据库里的时间不会自动转换,只会影响新写入的数据。开发期数据无所谓的,但如果是正式数据,需要手动做一次迁移更新。
5.5 端口被占用或“That port is already in use”
现象:执行runserver报错,提示8000端口被占用,或直接崩溃退出。
原因:上一次运行没关干净,或者有其他程序占了8000端口。
解决:改用其他端口启动即可,不必纠结于8000:
python manage.py runserver 8080如果想彻底解决,找到占用进程杀掉。macOS/Linux下用lsof -i :8000,Windows下用netstat -ano | findstr 8000,然后taskkill对应PID。更要养成一个好习惯:用Ctrl+C停止服务,而不是直接关终端窗口,否则进程容易残留。
6. 从“能跑”到“敢交”:上线前的安全加固与验证习惯
6.1 把SECRET_KEY和数据库配置从代码里拆出去
Django项目用SECRET_KEY做签名,泄露它意味着攻击者可以伪造会话Cookie、密码重置链接。我见过有人把整个项目传到公开仓库后,SECRET_KEY裸露在settings.py里,这是危险的。上线前至少做到两点:第一,SECRET_KEY从环境变量读取;第二,DEBUG强制为False并配置ALLOWED_HOSTS。
import os SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'dev-only-insecure-key') DEBUG = os.environ.get('DJANGO_DEBUG', 'True') == 'True' ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '*').split(',')逻辑说明:这套写法的逻辑是——本地开发时没设环境变量,自动用兜底值,不影响日常开发;部署时通过环境变量注入真实密钥,即使是同一份代码库,在不同环境里行为也不同。ALLOWED_HOSTS默认*表示接受任何域名访问,生产环境必须显式写成你的域名或IP,否则有HTTP Host头攻击风险。
6.2 用Django检查命令和本地验证清单
上线前跑一遍python manage.py check --deploy,Django会列出你还没做到的安全配置项,包括SECRET_KEY是否硬编码、DEBUG是否为True、ALLOWED_HOSTS是否为空等。这个检查不能保证部署一定成功,但能帮你扫掉90%的基础问题。
我给自己定的验证清单是这样的:
| 检查项 | 命令/方法 | 通过标准 |
|---|---|---|
| 数据表生成 | python manage.py migrate | 无错误输出 |
| 路由可达 | 访问/vote/和/admin/ | 页面正常加载 |
| 后台登录 | 用createsuperuser账号登录 | 能进入管理界面 |
| 表单提交 | 投一票并查看数据变化 | 票数+1且无403 |
| 静态资源 | 打开浏览器开发者工具Network面板 | CSS/JS无404 |
| 数据库备份 | python manage.py dumpdata > backup.json | 生成json文件 |
最后聊聊我的习惯:每次改模型前先想“这个改动会不会影响已存在的数据”,改完立刻makemigrations和migrate,代码提交前跑一遍python manage.py check。这套流程让我的项目从“能跑”变成“敢交”——交给导师、交给领导、交给用户,都不心虚。如果你刚上手Django,建议把这篇提到的代码从头敲一遍,不要复制粘贴。敲的过程中你会踩到那些只有动手才能遇到的错,那才是真正学会的时刻。希望帮到你。
本文还有配套的精品资源,点击获取