很多人问过我一个问题:Django到底是什么?尤其在Python圈子里,这个框架几乎绕不开——找工作看JD,上面写着"熟悉Django者优先";刷技术社区,满屏都是"Django项目实战";随手搜Python Web教程,十有八九也用它。但真要说清它能干嘛、为什么这么火、自己和它什么关系,我见过不少上手写过几年脚本的人照样支支吾吾。
Django从2005年发布到现在,已经快二十年了,依然活跃在各类Web项目的前线。它官方的宣传语是"The web framework for perfectionists with deadlines"(为追求完美且有截止日期的人准备的Web框架),这句话翻译成人话就是:它帮你把Web开发里最繁琐、最容易出错的部分全部打包解决,让你把精力集中在业务逻辑上。这篇内容就是给那些刚接触Python、被"框架"两个字吓住的新手,以及写了几年业务代码但没系统梳理过Django的老手看的,我会把它拆开揉碎,从它到底解决了什么问题、装好一个项目要几步、模型视图模板怎么配合工作、日常增删改查怎么玩,一直聊到它在真实企业项目里该承担什么角色。
1. 先解决最底层的问题:Django到底是个什么东西
"框架"这个词,是很多人跨不过去的第一道坎。你背过函数、写过脚本、知道怎么用requests抓个网页,但一听到"框架"就开始慌,觉得是个很高深的东西。其实没你想得那么玄乎。
我举个生活里的例子。你今天要开一家小饭馆,可以亲自买菜、洗菜、切菜、砌灶台、装修店面、设计菜单,这是自己搭全套。你也可以直接租一间已经通好水电、装好炉灶、甚至连菜谱模板都给你备好的商铺,你只需要选好招牌菜、洗干净盘子、把客人伺候好就行。Django就是后者。它是一个已经帮你把水电管线(HTTP协议处理)、灶台(URL路由)、传菜流程(请求-响应机制)、甚至库存系统(数据库操作)全部备好的厨房,你要做的,是往里面填你的菜谱——也就是业务代码。
从技术术语上讲,Django是一个重量级的Python Web框架,它遵循MTV(Model-Template-View,模型-模板-视图)架构模式。"重量级"不是说它跑得慢,而是说它自带的功能非常多、非常全。它内置了ORM(对象关系映射)、Admin后台管理、Form表单处理、用户认证系统、安全防护(防XSS、CSRF、SQL注入)、模板引擎、缓存框架、国际化支持,几乎你能想到的Web开发通用需求,它全给你塞进来了。这就是大家常说的"全家桶"。
这时候你可能疑惑:功能这么全,是不是很难学?恰恰相反。对一个刚从脚本思维转向Web开发的新手来说,Django的全家桶反而是最友好的——因为你不必去调研"这个功能用哪个库""那个功能怎么整合",框架都替你选好了一条最稳妥、最标准的路径,你遵循它的约定去写就完了。相比之下,Flask那种微框架就像一间毛坯房,自由度高,但什么都得自己装,马桶是你自己买的、水管是你自己接的,对新手来说反而是负担。
为什么全球有这么多开发者用它,从Instagram到Pinterest、从Mozilla到国内不少企业级应用都在跑Django?就因为它解决了Web开发里一个永恒的痛点:重复造轮子。同样的登录逻辑、同样的后台管理、同样的数据库增删改查,你每接手一个新项目都要重写一遍,不累吗?Django直接把这些沉淀成了框架内置的东西,让你从第一天开始就在写业务逻辑,而不是在写基础设施。
1.1 从MVC到MTV:Django的架构设计
你可能在网上看到过"MTV"这个词,有点懵:不是都说MVC吗?你的疑惑很正常。MVC是Model-View-Controller(模型-视图-控制器)的缩写,而Django的MTV对应的是Model-Template-View。名字看着差不多,其实有对应关系,但不完全一样。
按Django的官方定义,它自己就是MVC框架,只是把传统MVC里的View(视图)改叫了Template(模板),把传统MVC里的Controller(控制器)改叫了View(视图)。你记一个大概的对应关系就行:传统MVC的模型对应Django的模型,传统MVC的视图对应Django的模板,传统MVC的控制器则大体对应Django里视图函数和URL配置的组合。
那么这三个角色各负责什么?我拿前端开发里最熟悉的"用户提交一个表单"来举例。用户填写了一个商品入库表单,点了提交。这时候,Django的URL路由先发挥作用,它像个前台接待员,看清请求地址后把你带到对应的视图函数那里。视图函数接到请求后,读到表单数据,调用Model(模型)去跟数据库沟通——写入一条商品记录。数据库操作完成后,视图函数把结果打包,交给Template(模板)渲染成一个漂亮的HTML页面,然后返回给浏览器展示给用户。
整个过程里,Model管数据、Template管展示、View管调度。你不需要在HTML页面里写复杂的SQL,也不需要把数据库返回的数据手动拼成字符串,每一层各司其职,代码自然清晰。这也是新手最容易混淆的地方——一上来就试图理解"视图到底在哪个文件里写""模板又是干嘛的",其实多写两个项目就通了。
1.2 为什么说它是"带护栏的全家桶"
我还想给你打个预防针。很多人抱怨Django"太重""约束太多",这种声音主要集中在只做过小型脚本、没经历过大型项目的人身上。约束多,恰恰是Django最大的优点。
比如它强制你按Model、View、Template的目录结构组织代码,新手可能觉得烦,但如果你接手过那种代码堆在一个文件里、几万行乱成一团的项目,你就能体会到,Django的"约定优于配置"是在保护你。就好比高速公路有车道线,你开车不觉得被约束,反而觉得安全;真正可怕的是没有车道线的地方,看起来自由,实则处处是坑。项目到了一定复杂度,Django的强约束能帮你把代码维持在一个健康的组织结构里,这对团队协作和长期维护是决定性的。
2. 从零装一个Django项目:环境准备与目录结构
理论知识永远是抽象的,咱们直接动手。这一节我带你从空环境一路跑到页面显示"Hello Django",同时把Django项目里那些看起来奇奇怪怪的目录、文件一次性讲明白。顺带说一句,网上有个高频搜索词叫"怎样安装django",可见这一步卡住了不少人——不是卡在技术上,而是卡在"不知道装了什么、装到哪了"。
先说环境。强烈建议你在装Django之前先建一个虚拟环境,尤其当你的电脑上还有别的Python项目的时候。Python的包管理就像往书包里塞东西,你每个项目都往全局环境里pip install,过几个月就会变成一锅粥:项目A要Django 4.2,项目B要Django 5.0,全局环境里却只有旧版本一个,装新的就把旧的顶掉,项目A就崩了。虚拟环境相当于给每个项目开一个独立的小房间,互不干扰。
创建并激活虚拟环境、安装Django的命令在Windows和macOS/Linux下略有不同,我列一下核心命令:
# macOS / Linux python3 -m venv venv source venv/bin/activate # Windows python -m venv venv venv\Scripts\activate # 激活后安装Django pip install django装完建议顺手升级一下pip,用python -m pip install --upgrade pip,不然装第三方包时偶尔会踩到依赖解析的坑。安装完成后,你可以用python -m django --version确认版本号。这里提醒一句:Django 4.x和5.x都需要Python 3.10或更高版本,如果你的系统自带的还是Python 3.x老版本,先更新Python再玩Django,不然装不上。
2.1 startproject创建项目骨架
环境就绪后,在命令行里进入你想放项目的目录,执行:
django-admin startproject myproject cd myproject这一步会生成一个项目骨架,你尽量记住它的结构,这是你今后和Django朝夕相处的地方:
myproject/ manage.py # 项目命令行入口,所有管理命令都靠它 myproject/ __init__.py # 声明这是一个Python包 settings.py # 项目配置文件,数据库、应用、中间件都在这注册 urls.py # 全局URL路由表,请求进来先到这里分派 asgi.py # 部署到异步服务器时的入口 wsgi.py # 部署到传统Web服务器时的入口2.2 startapp创建应用:一个项目可以有多个"应用"
项目骨架做完,接下来是创建应用。Django里有个概念你从第一天起就要理解清楚:项目(Project)和应用(App)的区别。一个项目相当于一个大超市,应用相当于超市里的各个部门——生鲜部、日化部、收银处。一个Django项目通常包含多个应用,比如一个电商网站,用户管理是一个App,商品管理是一个App,订单管理又是一个App。它们通过项目的settings.py统一配置、协同工作。
创建应用的命令是:
python manage.py startapp products执行后项目里多出一个products/文件夹,里面有models.py(定义数据模型)、views.py(定义视图逻辑)、migrations/(数据库迁移记录)、admin.py(注册后台管理)、apps.py(应用配置)等文件。到这里,你已经完成了一个Django项目最原始的骨架搭建。很多人卡在"创建了app但页面还是404"这个坎上,多半是忘了在settings.py的INSTALLED_APPS里注册应用。打开myproject/settings.py,找到INSTALLED_APPS列表,加上'products'这个名字,Django才会认识你这个新应用。
2.3 跑起来,看第一个页面
配置完成后,先别急着写业务代码。启动一下开发服务器,验证你的环境和项目骨架是否健康:
python manage.py runserver看到命令行输出"But your browser doesn't work"相关的提示,别慌,那是提示你浏览器地址访问不对。默认地址http://127.0.0.1:8000/会显示一个带火箭图标的欢迎页,这就说明项目已经跑起来了。runserver是Django自带的重启式开发服务器,你改代码它自动热加载,相当省心。
"跑通第一个页面"这个目标,别急着上手就写视图,而是先理解一个请求从浏览器出发到你屏幕上这段路是怎么走的。URL路由表分一杯羹,然后视图函数处理逻辑,然后模板渲染HTML,三步缺一不可。这个链路你能默写出来后,接下来再写真实业务,就会有清晰的地图感。
3. 灵魂三件套:Model、View、Template是怎么配合的
现在到了Django最核心的部分——Model、View、Template的协同工作。我见过不少新手被这三者绕晕,一个常见的误区是:光顾着学某一个,比如疯狂折腾模板语法,却不知道视图怎么把数据塞给模板;或者深刻理解了模型,但不会用视图组织逻辑。其实你只要跟着一个完整的小例子走一遍,这个心结就解开了。
我们以"做一个产品列表页面"为例,就是把数据库里的商品名和价格,展示到网页上。
3.1 Model:定义数据结构
打开products/models.py,定义一个最简的商品模型:
from django.db import models class Product(models.Model): name = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name这里每一行都在向Django描述"商品长得什么样"。name是字符串、最长为100字符;price是十进制数,最多10位数字、其中2位是小数;created_at是创建时间,插入数据时自动填入。这就是ORM的用法——你用Python类来定义数据结构,Django负责把它翻译成创建数据库表的SQL语句。
定义完模型后,需要生成并执行数据库迁移,让数据库真的把这张表建出来:
python manage.py makemigrations products python manage.py migratemakemigrations生成迁移文件,记录你对模型的改动;migrate把改动真正应用到数据库。这是Django新手最容易含糊的一步——改完models.py不跑迁移,然后SQL里查不到数据,一直报错。记住这个命令组合就是"改模型必迁移"。
如果你想看看Django生成了什么SQL语句,可以用python manage.py sqlmigrate products 0001,这能让你直观地看到ORM替你写了什么。对一个想搞懂"ORM到底是怎么工作的"的人来说,这是一条捷径。
3.2 View:写业务逻辑的控制中枢
模型定义好以后,轮到写视图。Django有两种写法,函数视图和类视图,新手先从函数视图入手最直观。打开products/views.py:
from django.shortcuts import render from .models import Product def product_list(request): products = Product.objects.all() return render(request, 'products/product_list.html', {'products': products})这个函数做了两件事:第一,用Product.objects.all()从数据库把商品数据全捞出来;第二,把捞出来的数据通过render函数交给模板渲染,并把它作为HTTP响应返回给浏览器。注意render的第三个参数,是一个字典,它的作用是为模板提供数据——键是模板里用来引用的变量名,值是要传给模板的Python对象。
然后去项目下的urls.py,配置路由,把URL和视图函数对上:
from django.contrib import admin from django.urls import path from products import views urlpatterns = [ path('admin/', admin.site.urls), path('products/', views.product_list, name='product_list'), ]path('products/', views.product_list)的意思是:当用户访问/products/这个地址时,执行product_list这个函数。
3.3 Template:把数据渲染成好看的页面
最后一步,模板。在products应用下新建templates/products/目录,创建product_list.html文件:
<!DOCTYPE html> <html> <head> <title>商品列表</title> </head> <body> <h1>在售商品</h1> <ul> {% for product in products %} <li>{{ product.name }} - ¥{{ product.price }}</li> {% endfor %} </ul> </body> </html>刷新http://127.0.0.1:8000/products/,如果数据库里暂时没有商品,页面上就是空列表。你可以在Django shell里手动插入两条数据试试:
python manage.py shell进入交互式环境后:
from products.models import Product Product.objects.create(name='机械键盘', price='299.00') Product.objects.create(name='人体工学椅', price='1299.00')再次刷新页面,两条商品信息就会出现在列表里。走到这一步,你已经完整走通了MTV三件套的协作流程:Model定义数据,View从数据库取出数据,Template把数据渲染成页面。
4. 数据操作实战:以"删除对象"为引,把ORM的常用操作一次讲透
数据操作是Web开发的重头戏,热搜词里常年挂着"django执行查询-删除对象",原因很简单:会用ORM查数据、删数据,才算真正迈过了Django的门槛。这一节我结合一个高频场景,把ORM的创建、查询、修改、删除全套操作过一遍,顺便把几个容易踩坑的细节讲透。
还是用products应用里的Product模型举例。我先给你一个命令速查表,后面再逐个展开。
| 操作类型 | ORM写法 | 说明 |
|---|---|---|
| 创建一条记录 | Product.objects.create(name='咖啡', price='15.00') | 插入一条数据 |
| 查询所有记录 | Product.objects.all() | 返回QuerySet |
| 按条件过滤 | Product.objects.filter(name='咖啡') | 返回符合条件的QuerySet |
| 获取单条记录 | Product.objects.get(id=1) | 查不到或查到多条会报错 |
| 更新记录 | product.price = '18.00'; product.save() | 先查出对象,改字段再保存 |
| 删除记录 | product.delete() | 返回被删记录数和具体对象信息 |
4.1 创建:两种写法的区别
创建对象有两种经典写法,效果一样,但使用场景不同:
# 写法一:create,一步到位 Product.objects.create(name='散热支架', price='99.00') # 写法二:先实例化,再save,可以在保存前做更多字段处理 p = Product(name='显示器', price='899.00') p.full_clean() # 可选,校验字段合法性 p.save()实战中,如果只是简单插入,我倾向于用create;如果插入前需要对字段做一堆清洗、默认值处理,就先用实例化再save。顺带提一句,Product.objects.all()返回的类型叫QuerySet,这是个非常核心的概念——它本质上是一组数据库查询的集合,而且具有惰性。什么意思?当你写Product.objects.filter(price__gt='100')时,查询并没有立刻执行,只有当你真正去遍历它、把它转成列表、调用len()之类操作时,Django才会真正向数据库发SQL。这种设计能让ORM在组合多层过滤条件时变得高效,避免中间产生大量无用查询。新手不用深究优化层面,只要知道"QuerySet是懒的"这一点,后面理解代码行为才不会懵。
4.2 查询:filter和get的边界,新手最容易翻车
查询是日常开发里最频繁的操作。Product.objects.filter(name='咖啡')返回的是一个QuerySet,可能包含0条、1条或多条数据;Product.objects.get(name='咖啡')则要求精确匹配且唯一,如果查不到任何数据会抛Product.DoesNotExist异常,查到了两条或更多会抛Product.MultipleObjectsReturned异常。所以get一般用于按主键查单条记录,比如Product.objects.get(id=3),用filter来应对可能存在多条数据的场景更安全。
还有一个很实用的细节:filter的条件用的是双下划线语法。比如price__gt='100'表示价格大于100,name__icontains='咖啡'表示名称包含"咖啡"且不区分大小写,created_at__date='2024-05-20'表示按日期过滤。这套语法是Django ORM的精华,熟练掌握后查数据会非常顺手。我先上两个实际例子:
# 查询价格大于100的Commits商品 expensive = Product.objects.filter(price__gt='100') # 查询名称里包含"支架"的商品 stands = Product.objects.filter(name__icontains='支架')4.3 更新与删除:delete()的返回值里有文章
更新操作的核心是"先取对象,改字段,再save"。值得注意的是,如果你用filter拿到的是一整个QuerySet,可以批量更新:
Product.objects.filter(price__gt='500').update(price='499.00')一条SQL就把所有高价商品价格改成499,这在效率上比循环save强太多。
删除操作是这个热搜词的主角。删一条数据的写法是:
product = Product.objects.get(id=1) product.delete()值得留意的是,delete()的返回值非常有信息量。它返回一个(total_deleted, {模型名: 数量})这样的元组。比如(1, {'products.Product': 1}),第一个数字是总共删掉了多少条记录,第二个字典则明细列出每个模型删了多少条。为什么会有多个模型呢?因为Django默认在模型外键关系上做级联删除——你删一个商品分类,该分类下的所有商品都给删了;如果你开启了on_delete=models.PROTECT之类的保护式外键,删除行为又会不同。所以删数据之前,务必想清楚级联效应,这是很多线上事故的根源。
还有一个批量删除的坑:对QuerySet直接调delete()是批量删除,如Product.objects.filter(price__lt='10').delete(),它会删除所有满足条件的数据,且不会逐一调用每个对象上的任何删除逻辑。慎用,最好先select一条出来看看条件是否符合预期。
4.4 在真实页面里组合这些操作
光在shell里操作刷成就感,很多人还是不知道要在哪写这些代码。我给你一个非常实用的示例:在视图里把"删除商品"做成一个带确认的跳转。
from django.shortcuts import get_object_or_404, redirect def product_delete(request, product_id): product = get_object_or_404(Product, id=product_id) if request.method == 'POST': product.delete() return redirect('product_list') return render(request, 'products/product_confirm_delete.html', {'product': product})然后在urls.py加一条路由:
path('products/<int:product_id>/delete/', views.product_delete, name='product_delete'),<int:product_id>是Django路由里的路径参数,它会把URL里的数字自动转换成整数传给视图函数。这样,你的列表页里每个商品后面放一个"删除"按钮,点击后先到确认页,POST确认后真正删除,再跳回列表。这套流程就是Django开发里最基础的CRUD闭环。等你熟练了这套操作,再去用Django视图类的CreateView、DeleteView,会发现那只是把这里的逻辑封装成了更高层的方法集成。
5. 真实开发中的Django:它在企业级项目里该站哪班岗
聊完技术细节,我必须把视角拉高一点。你学了Django的基础增删改查、跑通了MTV流程,但这距离"用它做一个企业级项目"还有一段认知上的距离。这一节我聊些我在项目里踩过、看别人踩过之后的体会。
先明确一点:Django在企业级Python项目里最常见的职责有两个——做业务管理后台和做Web API后端。尤其是"Python Web企业级项目开发教程"这个搜索词背后隐含的需求,其实不是再学一遍基础语法,而是"我该怎么把一个正经的业务系统拆成模块化设计"。Django的Admin后台功能本身就是企业标配,你只要把模型注册进admin.py,一套包含增删改查、筛选、权限控制的后台界面就出来了。这个做内部运营工具,能帮你省下好几个月工期。它的用法如下:
# products/admin.py from django.contrib import admin from .models import Product admin.site.register(Product)创建超级用户并启动服务器后,访问http://127.0.0.1:8000/admin/,后台界面直接可用。以前我给一个传统企业做库存管理系统,就是靠Django Admin先在三天内上线了内测版,再逐步根据业务需求定制自己的管理页面。
再说API后端。现在的前后端分离开发大行其道,如果你用Django只做后端,通常不会直接返回HTML页面,而是返回JSON数据。这时候你会用到Django REST framework(DRF)——它基于Django,专门用来快速构建RESTful API。DRF的序列化器(Serializer)、认证机制、视图集(ViewSet)能大幅简化API的开发过程。一个简单的例子:
from rest_framework import serializers, viewsets from .models import Product class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = '__all__' class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer配上路由注册,一个支持增删改查的完整API接口几十行代码就能搞定。这是Django在企业级市场经久不衰的核心原因:从Model定义到接口暴露,节奏非常顺滑。
5.1 Django不太适合的舞台
虽然我把Django夸得挺多,但它的边界也得讲清楚,不然你会在错误的地方用了它。Django的请求处理模型偏传统,每来一个请求就把它放进一个同步的请求-响应周期里,长连接、高并发的实时推送类场景(比如聊天系统、实时协作白板、海量物联网数据流)不是它的主战场。这种活儿通常会交给异步框架(如FastAPI、Tornado),或者单独用消息队列与专用服务来处理,Django则负责它擅长的业务管理、数据持久化和接口编排。
还有个常见的误区是"Django单体化撑不起大流量"。其实单体的Django配合Celery做异步任务、Redis做缓存、数据库读写分离,把业务拆成模块化App,它完全能扛住中等规模的并发访问。说白了,大部分项目的瓶颈从来不在框架,而是在SQL设计、缓存策略和部署架构上。先别一上来就微服务,系统还没烧起来,拆分倒是把自己先拆晕了。
5.2 用AI辅助写Django代码,这件事该怎么看
热搜词里有一个"用ai agent开发django",我在这儿多说两句。现在AI辅助编程确实已经进入不少团队的日常,AI能帮你快速生成CRUD代码、补全配置文件、甚至根据自然语言描述出来一整个Django App的雏形。我自己也用,确实能提速,尤其适合那些模式化很强的代码。但这里有个残酷真相:**AI生成的代码质量,取决于你对Django本身的理解深度。**如果你搞不懂QuerySet惰性、搞不清模型迁移、看不懂DRF的序列化流程,AI生成的代码出bug时,你连错误提示都读不懂,更别提修复了。所以对新手的建议很明确:初期一定要手工把基础链路写烂写熟,把Django的原理和内功练好,再把AI当加速器用,它突然会变得很好用。
6. 新手最常踩的坑,和我给你的学习路线
最后这部分,我把这些年我在各种群聊里看到的新手高频错误总结成一张表,每一个我都亲身踩过,或者看过别人流着泪爬出来:
| 常见坑 | 错误现象 | 正确做法 |
|---|---|---|
| 忘了跑迁移 | 表不存在、字段报错 | 每次改模型后执行makemigrations+migrate |
| 忘了注册App | 模板找不到、URL报错 | settings.py的INSTALLED_APPS加入App名称 |
| settings.py的DEBUG | 部署后被用户看到隐私报错 | 上线必须设为DEBUG=False并用正规服务器跑 |
| 时区问题 | 时间显示和本地差几个小时 | setting里的USE_TZ=False或正确配置TIME_ZONE |
| 模板语法错误 | 页面空白或传来传去全是{} | 先print传进render的上下文,再检查模板变量名 |
| 静态文件404 | CSS、JS都加载不出来 | 开发阶段配置STATICFILES_DIRS,并确认模板用{% load static %} |
| 代码一股脑堆在views.py | 一个文件几千行,维护噩梦 | 按App/service划分逻辑,把可复用的业务抽成单独模块 |
这些坑有个共同点:几乎都不是"不会写"而是"不知道流程"。“不知道流程”这个事儿,真得靠大量的实际项目来补课——你光看教程、光读官方文档,永远没法建立肌肉记忆,一旦换到新环境还是会懵。我的建议是啃透一个真实的完整项目,哪怕是从零开始做一个简单的博客系统,也一定要亲自经历从建项目、建App、写模型、跑迁移、写视图、套模板、接Admin、部署上线的全过程。这个过程走完一遍,Django的骨架才算焊进了你的技能树里。
接下来你可以按这个顺序进阶:先熟练函数视图和QuerySet,然后学DRF做API,再学Django的高级特性(信号、Celery异步任务、缓存、权限系统),每一步都跟着项目走,而不是跟着文档走。学习资料方面,官方文档仍然是最好的选择——虽然它的英文文档读起来有些枯燥,但精确、全面。再搭配一两本实战书就足够,不需要囤教程。还记得去年有个学员跑来找我,说他囤了十几个小时的视频课,听的时候神清气爽,一动手就废。这太正常了,编程这东西,"听懂了"和"会写了"之间,隔着几百次报错的距离。
最后分享一个我自己的小习惯:每学一个新框架,我都会准备一个"万能Demo项目"。这个项目麻雀虽小五脏俱全,包含了认证、增删改查、权限、API这些常见模块,技术更新后我会随手把它维护一下。有了它,我接任何新项目都能快速起步,也方便对比不同版本的API差异。
技术圈总说Django老了,可它依然是Python Web世界里迭代得最稳、生态最全的选择之一。框架的"好坏"其实没那么重要,重要的是你自己能不能把一个想法,顺着它给出的轨道,干净利落地变成能跑的东西。希望这篇内容能帮你少走些弯路。