什么是Django
Django简介
Django是Python Web开发的主流框架,它鼓励快速开发和简洁、实用的设计,旨在帮助开发人员尽快将应用程序从概念到完成。
Django的官网地址:https://docs.djangoproject.com/zh-hans/4.2/
Django目前的最新长期支持版本是4.2,所以这里采用4.2版本
在Web开发领域最著名的设计模式为MVC,而Django的设计模式被叫做MTV。其实本质跟MVC是一样的,只是叫法不同。
MVC是Model-View-Controller的缩写,
- M(model) 数据存储层即业务逻辑层,代表一个存储数据的对象
- V(view) 视图层,代表模型数据的可视化
- C(controller) 控制层,作用于模型和视图上。它控制数据流向,并在数据变化时更新视图。它使视图与模式解耦。
Django借鉴了经典的MVC模式,也就是MTV设计模式
- M(Model 模型) 与MVC中的M功能相同,负责和数据库交互,进行数据处理
- T(Template 模板) 与MVC中的V功能相同,负责构造要返回的html页面
- V(View 视图) 与MVC中的C的功能相同,接收请求,进行业务处理,返回响应
Django中数据流转和请求流程如下:
- 用户通过浏览器发起request请求;
- 通过View根据请求和业务逻辑向model获取或发送数据;
- model根据view的要求与数据进行交互;
- view将收到model返回的结果发送给template;
- template将接收到的数据渲染成最终的html返回给view层;
- View层将最终渲染后的即包含结果数据的html返回给用户;
Python的web开发框架
Python常用的web开发框架有几十个,最常用的有三个:Django,Flask,Fastapi。(本来写了第四个Tornado,但查了一下使用率,相较于前面3个量太少了。)
后续的平台系统设计都会使用Django来完成。
Django的安装
Django与Python的版本匹配
官网上有明确的描述:https://docs.djangoproject.com/zh-hans/4.2/faq/install/#faq-python-version-support
| Django版本 | Python版本 |
|---|---|
| 3.2 | 3.6,3.7,3.8,3.9,3.10 (在3.2.9版本中加入支持) |
| 4.0 | 3.8,3.9,3.10 |
| 4.1 | 3.8,3.9,3.10,3.11(在4.1.3版本中加入支持) |
| 4.2 | 3.8,3.9,3.10,3.11 |
新电脑安装的是Python3.11.x,这里Django选择最新的长期支持版4.2.x。
安装Django
安装Django可以通过命令行安装,也可以在IDE中创建项目时进行安装。
虚拟环境
在进行python项目开发过程中,建议使用虚拟环境。这样可以隔离各个项目环境及避免相互造成的不良的影响。比如新开发的Web应用项目使用的是Django4.2,而之前的某个旧系统用的是Django3.2,某个大数据分析项目使用的numpy、pandas等需要安装上百个依赖包,而这些包对Django应用项目来说可能是无用的,等等。若不使用虚拟环境,势必会造成开发环境的混乱。
这里选择的虚拟环境软件是:virtualenvwrapper。它会将所有创建的虚拟环境目录安放到自定义指定的目录下。而其他的虚拟环境软件都是将虚拟环境创建在当前目录下,很容易造成误操作,创建到了不应该创建的目录下。
安装virtualenvwrapper
安装命令:
pip install virtualenvwrapper
安装完成后,在没有自定义指定虚拟环境目录时,默认的虚拟环境路径是当前创建虚拟环境用户目录/Users/laobai下的Envs目录。
自定义指定虚拟环境路径
在系统环境变量中增加一项WORKON_HOME,设置为需要指定的目录即可。
vim .zprofile
export WORKON_HOME=/Users/laobai/workspaces
source .zprofile
创建虚拟环境
mkvirtualenv djangotest
创建虚拟环境后,会自动进入刚刚创建的这个虚拟环境中。
进入/切换到某个虚拟环境
workon djangotest
退出当前虚拟环境
deactivate
删除某个虚拟环境
rmvirtualenv mytest
列出所有虚拟环境
lsvirtualenv
命令行安装
进入到django项目的虚拟环境中
workon djangotest
执行安装命令,当前最新版本是4.2.6,可以指定版本安装,也可以默认安装最新版本
pip install django
pip install django==4.2.6
命令行创建Django项目
django-admin startproject djangotest01
IDE安装
开发Python应用有多款IDE可以选择,这里选择Pycharm Professional(需购买授权)。(之后所有文章中提到的Pycharm,都特指“Pycharm Professional”)
通过Pycharm创建的Django项目,会自动安装最新版本的django。
基础初始化
(这里是已经创建好了项目,但还没有做任何设置、处理)
配置MySQL
Django默认配置的是sqlite3,由于之后的所有开发应用基本都使用MySQL数据,所以这里将sqlite3配置改为mysql的配置。
MySQL的安装:略。(这里使用的是docker,mysql的版本为8.0.29)
settings.py配置
在settings.py的配置文件中,注释掉sqlite3的配置,增加mysql数据库的配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djs01',
'USER': 'root',
'PASSWORD': 'xxxxxxx',
'HOST': '127.0.0.1',
'PORT': '3306'
}
}
数据库驱动
python操作mysql有多个驱动程序,这里使用mysqlclient
pip install mysqlclient
安装mysqlclient可能会出现错误,可以尝试下面的方法进行解决
安装mysql (由于使用的是docker的mysql,本机是没有安装mysql的)
brew install mysql安装pkg-config
brew install pkg-config
Django常用命令
创建项目
django-admin startproject djangotest01 // 其中djangotest01为项目名称
创建APP
app是django项目的组成部分。一个app代表项目中的一个模块,所有url请求的响应都是由app来处理的。即一个django项目由多个app组成,一个app可能被用到多个项目中。 (命令行工具中,需要使用python命令,若是在IED中的命令行不需要python命令,直接使用manage.py即可。以下命令都是如此。)
python manage.py startapp blog
数据迁移
django中经常使用model创建模型,然后将模型导入数据库,会使用到下面两个命令
将模型转化为ORM
python manage.py makemigrations
执行迁移命令
python manage.py migrate
运行服务
python manage.py runserver
Django version 4.2.6, using settings 'djangoProject01.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
上面的命令默认使用的是8000端口,若要使用其他端口号
python manage.py runserver 8001
Django version 4.2.6, using settings 'djangoProject01.settings'
Starting development server at http://127.0.0.1:8001/
Quit the server with CONTROL-C.
若需要局域网的其他用户可以访问这个服务,需在启动时使用0.0.0.0
python manage.py runserver 0.0.0.0:8001
Django version 4.2.6, using settings 'djangoProject01.settings'
Starting development server at http://0.0.0.0:8001/
Quit the server with CONTROL-C.
此时浏览器访问http://192.168.18.175:8181/ ,可能会报错
DisallowedHost at /
Invalid HTTP_HOST header: '192.168.18.175:8181'. You may need to add '192.168.18.175' to ALLOWED_HOSTS.
Request Method: GET
Request URL: http://192.168.18.175:8181/
Django Version: 4.2.5
Exception Type: DisallowedHost
Exception Value:
Invalid HTTP_HOST header: '192.168.18.175:8181'. You may need to add '192.168.18.175' to ALLOWED_HOSTS.
Exception Location: C:\Users\sunchanghai\Envs\djangotest1\lib\site-packages\django\http\request.py, line 150, in get_host
提示中已经明确给出了解决方法。
在settings.py做如下设置
ALLOWED_HOSTS = ['192.168.18.175']
再次访问即可成功访问到对应的页面了。
Django项目目录结构
下面是Django项目的文件目录结构
其中:
- 顶层的djangoProject01:Django项目目录,项目的所有文件都存放在这里面
- manage.py:Django项目的命令行工具,用于执行各种管理任务,创建应用、运行服务、执行数据迁移等;
- djangoProject01:项目的主目录,通常与项目的名称相同;
- settings.py:包含项目的设置和配置,如数据库连接、静态文件路径、应用程序列表等;
- urls.py:定义URL路由规则,将URL映射到相应的视图函数;
- asgi:py:ASGI服务器配置文件,用于异步Web服务器;
- wsgi.py:WSGI入口文件,用于将Django应用程序与Web服务器连接起来;
- blog、crm:项目的应用app目录,一个Django项目会有多个应用app;
- migrations/:包含数据库迁移文件的目录,Django使用迁移来管理数据库模式的变化;
- admin.py:用于配置应用程序在 Django后台管理界面中的显示和行为;
- apps.py:应用程序的配置文件,包含有关应用程序的元数据;
- models.py:定义应用程序的数据模型,通常使用Django的ORM功能。
- views.py:定义处理HTTP请求并生成HTTP响应的视图函数;
- urls.py:app中的urls,实现url的多层解析,减少主urls.py中的复杂度;
- tests.py:用于编写应用程序的测试代码;
- static:存放静态文件
- template:存放模板文件
配置settings.py
ALLOWED_HOSTS
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS,允许的主机,默认为空值。用于配置能够访问当前网站的域名或IP。
DEBUG为True时,ALLOWED_HOSTS可以为空,也可以设置为[‘127.0.0.1,’localhost’]
DEBUG为True时,ALLOWED_HOSTS必须填值。一般是服务器公网IP或域名。
尽量不要设置为ALLOWED_HOSTS = [*]。
INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'crm',
'blog',
]
只要创建了app,就需要将app的名字追加到INSTALLED_APPS中,需用逗号分割。
本地化设置
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
静态文件
增加静态文件目录设定
STATIC_URL = 'static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
Web应用开发模式
Web应用开发模式主要有两种:前后端不分离和前后端分离
前后端不分离
前后端不分离,是以后端直接渲染模版完成响应的一种开发模式。
浏览器向服务器端发起请求,服务器接收到请求后去数据库中获得数据,然后渲染html模板并返回渲染后的html数据,或返回一个重定向。绝大部分工作都子后端进行处理,客户端(前端)只负责页面的展示和用户交互。
前后端分离
随着ajax技术的出现,可以在不刷新页面向服务器端发送http请求,就出现了前后端分离的开发模式。
后端只需要开发接口即可,前端可以通过发起ajax请求,拿到后端的数据,如何渲染和调用接口的事由前端负责。
前后端分离的开发模式,是当下主流的开发模式。
路由
Django中路由的作用和路由器类似,当一个用户请求站点的一个页面时,路由系统通过对url的路径部分进行匹配,匹配成功就会执行对应的视图来返回响应结果。
django处理请求的过程:
当接收到一个请求,django首先会到settings文件中查找根URLconf模块ROOT_URLCONF = 'djangoProject01.urls',在这个模块中查找路由匹配规则。django加载该模块查找变量urlpatterns,urlpatterns列表中定义了url和视图函数的对应关系,它是django.urls.path()和/或django.urls.re_path()实例的列表。django会按顺序运行每个url模块(包括每个应用下的urls.py),并在与请求的url匹配成功的第一个url停止,django将导入url并调用对应的视图。
根路由模块,Django通过设置ROOT_URLCONF指定的主路由模块,通常是项目主目录下的urls.py文件。
子路由模块,各自应用目录下的urls.py都是子路由.
path()
语法格式:path(route, view, kwargs=None, name=None)
其中:
- path():函数防护一个对象,表示一个路由规则;
- route:一个字符串,表示url规则;
- view:一个视图;
- kwargs:一个字典,需要传入的额外参数;
- name:url的命名;
实例:
首先创建两个APP
manage.py startapp user
manage.py startapp goods
分别在两个应用中的views.py中写个简单的页面返回
user-views.py
from django.http.response import HttpResponse
def user(request):
return HttpResponse('我是用户页面!')
goods-views.py
from django.http.response import HttpResponse
def goods(request):
return HttpResponse('我是商品页面!')
在主目录下的urls.py中配置路由
from django.urls import path
from user.views import user
from goods.views import goods
urlpatterns = [
path('user/', user),
path('goods/', goods),
]
上面的代码有一处需要特别注意,path('user/', user),path()函数的第二个参数,是url对应的函数名称user,即内存地址,而不是user()。千万不要带括号,带括号是执行user函数,并将执行后的返回结果作为path()函数的第二个参数。
启动服务,分别访问 http://127.0.0.1:8000/user/ 和 http://127.0.0.1:8000/goods/ 两个url地址,能够正确显示views视图文件中返回的内容。
比如 http://127.0.0.1:8000/user/ 这个url请求发送到服务端,会交给路由模块即URLconf,也就是上面的urls.py。首先会从url端口号后面的/切割,剩下的部分user/在urlpatterns中进行匹配,匹配成功即会触发对应的视图函数,若匹配不成功就会报错或进行错误处理等。
另外需要注意,进行匹配时,只匹配url路径,url中的参数不会参与匹配。比如http://127.0.0.1:8000/user/?username="laobai",还是匹配user/参数不会参与匹配,也就是说URL无论是POST请求还是GET请求等,路由都是一样的,都会路由到同一个视图函数。
include()
当项目创建的app较多时,urls.py中的urlpatterns会配置的比较复杂,通常会在每个app的目录下创建一个urls.py文件(这个文件在创建app不会自动创建),各个app目录下的urls.py叫做子路由。使用include()方法来将子路由导入的一个URLconf模块中。通过使用include()将子路由包裹到父路由中,也被称作为路由分发。
语法格式:include(module, namespace=none)
其中:
- module:URLconf模块(或模块名称)
实例:
将上面创建的user和goods两个app通过include()来配置子路由
分别在user app和goods app下创建urls.py
user-urls.py
from django.urls import path
from user.views import user
urlpatterns = [
path('', user),
]
goods-urls.py
from django.urls import path
from goods.views import goods
urlpatterns = [
path('', goods),
]
修改项目主目录下的urls.py
from django.urls import path, include
urlpatterns = [
path('user/', include('user.urls')),
path('goods/', include('goods.urls')),
]
这样就将user/和子路由user.urls进行映射,goods/和子路由goods.urls进行映射。
再次分别访问 http://127.0.0.1:8000/user/ 和 http://127.0.0.1:8000/goods/ 两个url地址,都可以正确返回结果。
在URL中捕获参数
django允许在url中捕获值,若要从url中捕获值,需要使用<>尖括号。这里的参数是指用某个参数作为url中两个/中间的内容。尖括号定义变量名,捕获的值传递给视图函数相同名称的参数,具体格式<转换器:参数名>。
例:
user/views.py
from django.http.response import HttpResponse
def user(request):
return HttpResponse('我是用户页面!')
def detail(request, pk):
return HttpResponse('id为{}的用户详情'.format(pk))
user/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.user),
path('<int:pk>/', views.detail),
]
上面代码中<int:pk>即为需要匹配捕获的内容,而冒号前面的int被称作路径转换器。
路径转换器一般有5种:
str:匹配除了路径分隔符/之外的任何非空字符串。如果<>尖括号内不包含转换器,默认为字符串str转换器;int:匹配0或任何整数。返回一个整数类型;slug:匹配任何由ASCII字符或数字组成的slug字符串,加上连字符和下划线;uuid:匹配格式化的UUID(必须含破折号并且必须是小写字母);path:匹配任何非空字符串,包括路径分隔符/。这就允许匹配完整的URL路径。
上面的示例中是接收一个参数,也可以接收多个参数。
user/views.py
from django.http.response import HttpResponse
def user(request):
return HttpResponse('我是用户页面!')
def detail(request, pk):
return HttpResponse('id为{}的用户详情'.format(pk))
def user_list(request, year, month):
return HttpResponse('{}年{}月注册的用户列表'.format(year, month))
user/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.user),
path('<int:pk>/', views.detail),
path('<int:year>/<int:month>/', views.user_list)
]
对应的访问路径为:http://127.0.0.1:8000/user/2022/03/
re_path()
语法格式:path(route, view, kwargs=None, name=None)
其中:
- re_path():函数防护一个对象,表示一个路由规则;
- route:一个字符串,表示一个url规则,字符串中需包含正则表达式;
- view:一个视图;
- kwargs:一个字典,需要传入的额外参数;
- name:url的命名;
与path()不同的是,route部分包含正则表达式。当进行匹配时,从正则表达式中捕获的内容会被传递到视图中。
例子,将上面的用户注册年月列表的urls.py改写为如下:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('', views.user),
path('<int:pk>/', views.detail),
# path('<int:year>/<int:month>/', views.user_list),
re_path(r'(?P<year>[1-2]\d{3})/(?P<month>0?[1-9]|1[0-2])/', views.user_list),
]
url命名
path()和re_path()有个参数name,这个参数可以给url命名。
name参数的作用,是可以通过django.shortcuts.reverse('name')来反向解析出url的路径
user/urls.py增加name参数,并新增login url:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('', views.user, name='user'),
path('<int:pk>/', views.detail, name='user_detail'),
# path('<int:year>/<int:month>/', views.user_list, name='user_list'),
re_path(r'(?P<year>[1-2]\d{3})/(?P<month>0?[1-9]|1[0-2])/', views.user_list, name='user_list'),
path('login/', views.login,name='login')
]
user/views.py增加login视图函数,登录后跳转到用户页面。可以通过reverse('user')来反向解析出对应的url,而不是写死为/user/
from django.http.response import HttpResponse
from django.shortcuts import reverse, redirect
def user(request):
return HttpResponse('我是用户页面!')
def detail(request, pk):
return HttpResponse('id为{}的用户详情'.format(pk))
def user_list(request, year, month):
return HttpResponse('{}年{}月注册的用户列表'.format(year, month))
def login(request):
return redirect(reverse('user'))
有一个或多个参数的形式
def login(request):
return redirect(reverse('user_detail', args=(11,)))
def login(request):
return redirect(reverse('user_list', kwargs={"year": 1999, "month": 11}))
App命名
一个项目会有多个app,每个app下面的的url可能会有相同的情况,比如user/detail/和goods/detail两个不同app,user和goods有相同的url/detail/。为了避免跳转页面出错,会为每个app定义一个app_name,在执行reverse()时带上app_name
user/urls.py中增加app_name
from django.urls import path, re_path
from . import views
app_name = 'user'
urlpatterns = [
path('', views.user, name='user'),
path('<int:pk>/', views.detail, name='user_detail'),
# path('<int:year>/<int:month>/', views.user_list, name='user_list'),
re_path(r'(?P<year>[1-2]\d{3})/(?P<month>0?[1-9]|1[0-2])/', views.user_list, name='user_list'),
path('login/', views.login)
]
goods/urls.py中增加app_name
from django.urls import path
from goods.views import goods
app_name = 'goods'
urlpatterns = [
path('', goods),
]
user/views.py中进行反向解析的reverse()中url名称前需要加上app_name
def login(request):
return redirect(reverse('user:user_list', kwargs={"year": 1999, "month": 11}))
视图
视图是MTV模式中的View层,用于处理客户端的请求并生成响应数据。在Django中,视图有两种:FBV(function base views),基于函数的视图,就是在视图里使用函数处理请求,CBV(class base views),基于类的视图,就是在视图里使用类处理请求。
FBV(函数视图)
基于函数的视图,user/views.py
from django.http.response import HttpResponse
from django.shortcuts import reverse, redirect
def user(request):
return HttpResponse('我是用户页面!')
def detail(request, pk):
return HttpResponse('id为{}的用户详情'.format(pk))
def user_list(request, year, month):
return HttpResponse('{}年{}月注册的用户列表'.format(year, month))
def login(request):
return redirect(reverse('user:user_list', kwargs={"year": 1999, "month": 11}))
配置user/urls.py
from django.urls import path, re_path
from . import views
app_name = 'user'
urlpatterns = [
path('', views.user, name='user'),
path('<int:pk>/', views.detail, name='user_detail'),
# path('<int:year>/<int:month>/', views.user_list, name='user_list'),
re_path(r'(?P<year>[1-2]\d{3})/(?P<month>0?[1-9]|1[0-2])/', views.user_list, name='user_list'),
path('login/', views.login),
]
CBV(类视图)
基于类的视图,可以与特定的HTTP方法关联,能通过单独的方法替代条件分支的方式处理不同的HTTP请求。goods/views.py
from django.http import HttpResponse
from django.views import View
class Goods(View):
def get(self, request):
print("Get请求")
return HttpResponse("Get请求")
def post(self, request):
print("Post请求")
return HttpResponse("Post请求")
配置goods/urls.py
from django.urls import path
from goods import views
app_name = 'goods'
urlpatterns = [
path('', views.Goods.as_view()),
]
请求和响应
web框架本质就是处理用户发起的请求,然后返回响应结果。请求和响应就是框架中的数据流。
请求
当页面被请请求时,django会创建一个HttpRequest对象,该对象包含关羽请求的元数据。然后django加载相应的视图,将HttpRequest对象作为第一个参数传递给视图函数。每个视图负责返回一个HttpResponse对象。
请求参数类型
Django前端向后端传参的方式有以下几种:
- 拼接在url后面的参数(
?key=value) (可以通过request.GET属性获取) - 请求体参数
- form表单格式(可以通过
request.POST属性获取) - json格式(可以通过
request.body属性获取) - 上传文件(可以通过
request.FILES属性获取)
- form表单格式(可以通过
- 请求头数据
- 路径参数:url路径中的参数<>
request 对象的常用属性和方法有:
- request.content_type : 获取请求正文类型
- request.encoding : 请求编码
- request.is_ajax : 请求是否是 ajax 请求
- request.GET : 获取GET请求信息
- request.POST : 获取POST请求信息
- request.FILES : 获取文件信息
- request.COOKIES : 获取COOKIE信息
- request.path : 获取请求路径信息
- request.body : 获取请求体(字节类型)
- request.META : 客户端的元信息(环境信息)
响应
每个视图都需要实例化,填充和返回一个响应对象。响应对象可以直接创建,一般有HttpResponse、HttpResponseRedirect、JsonResponse 三个类,当然也可以使用 render()、redirect() 等函数快速创建。
from django.http.response import HttpResponse
from django.http.response import JsonResponse
from django.shortcuts import reverse, redirect, render
传字符串
可以将页面的内容以字符串的形式传递给HttpResponse构造函数,也可以增量的形式添加内容。
from django.http.response import HttpResponse
def user(request):
return HttpResponse('我是用户页面!')
from django.http.response import HttpResponse
response = HttpResponse()
response.write("<p>这是用户页面!</p>")
设置headers
使用HttpResponse.headers设置或删除一个字段
from django.http.response import HttpResponse
response = HttpResponse()
response.headers['token'] = '1234567'
del response.headers['token']
设置状态码
直接传递参数status给构造函数
from django.http.response import HttpResponse
def user(request):
return HttpResponse('我是用户页面!', status=200)
JsonResponse对象
格式:class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_dupms_params=None, kwargs)
JsonResponse是HttpResponse的一个子类,用来创建json编码的响应。默认的Content-Type为application/json。第一个参数data应该是字典实例。如果safe参数设置为False,它可以是任何json可序列化的对象。
from django.http.response import JsonResponse
data = {'name':'laobai', 'age':18}
JsonResponse(data)
对字典以外的对象进行序列化,需要将safe参数设置为False,否则就会引发错误。