可以使用 pip install django 来安装 Django 库
可以通过终端和 pycharm 两种方式来创建项目,区别是 pycharm 创建的项目会额外添加了一些模板信息。
Django 创建项目有点类似于 Scrapy ,在要创建项目的文件夹下,使用终端(命令行)输入命令。django_admin.exe 是在 python 下的 Scripts 下,默认添加到了环境变量中,所以可以直接执行。
django-admin startproject 项目名称
执行完成后,会在当前目录下创建一个项目名称命名的文件夹。这样项目就创建成功了。
在 pycharm 中新建项目时,选择 Django 项目,即可创建 Django 项目了。需要注意的是,使用企业版的 pycharm 。
创建完成的项目目录结构为(learnDjango是项目名称):
learnDjango
│ manage.py
│
└─ learnDjango
asgi.py
settings.py
urls.py
wsgi.py
__init__.py
一个项目下可以有 N 个独立的 app 来处理不同的功能,每个 app 可以拥有独立的、不同的表结构、函数、HTML模板、CSS等。
终端里,创建好的项目文件夹下,带参数运行 manage.py 即可创建一个新的 app:
python manage.py startapp app名称
app 的目录结构为(app01是app的名称)
app01
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│
└─migrations
__init__.py
当创建了项目和app之后,到应用启动还需要注意以下几点:
在 app 目录下的 apps.py 中能看到类名称,通常是 app项目名+Config。将此类名称添加到 settings.py 里的 INSTALLED_APPS 列表中即可。需注意的是添加目录名和文件名,例如 ‘app01.apps.App01Config’
在 urls.py 中编写对应关系。添加到 urlpatterns 列表中,格式为
path('路由路径', 函数名)。例如 path(‘index/’, views.index)。另外注意的是,需要导入 views.py 文件。
在 views.py 中编写视图函数。需注意的是,视图函数默认需有形参 request。例如:
from django.shortcuts import render,HttpResponse
# Create your views here.
def index(request):
return HttpResponse("初学 Django")
在终端中输入 python manage.py runserver 来启动 django 服务,使用 ctrl + c 来结束服务
使用 pycharm 建立的 django 项目可以通过默认启动项直接启动,默认快捷键是 shift + F10
在视图函数中使用 render() 方法可以返回 html 页面
def index(request):
return render(request, 'index.html')
默认情况下 django 会在 app 目录下的 templates 目录下来查找需要使用的 html 页面文件。
需要注意的是,django 并不是在当前 app 的 templates 目录下寻找,而是根据 app 的注册顺序,查找相应 app 目录下的 templates 目录下查找 html 文件。
另外,可以在项目 settings.py 文件中的 TEMPLATES 字段下的 ‘DIRS’ 添加参数 [os.path.join(BASE_DIR, 'templates')] ,则优先在项目根目录下的 templates 目录下查找 html 文件。
在开发过程中,一般将图片、CSS、js 等作为静态文件处理。django 会将静态文件存放在 app 目录下的 static 文件夹下。html 指向静态文件时使用相对路径,例如 src="/static/1.png"。所以一般 static 目录下会创建 img、js、css、plugins 等文件夹存放相应的静态文件。
不过 django 推荐这样使用静态资源:
{% load static %}
<link rel="stylesheet" href="{% static 'plugins/bootstrap-5.1.3-dist/css/bootstrap.css' %}">
如果需要更改 static 目录,则在 settings.py 文件里的 STATIC_URL 里更改。
模板语法本质上是写 HTML 时写一些占位符,由数据对占位符进行替换和处理。
django 的模板语法是包含在 { } 中的。
使用 render() 方法时,可以添加一个字典参数,将需要传递到 html 中变量的变量名和值写入:
def index(request):
text = '初学 Django'
return render(request, 'index.html', {"n1": text})
在页面中使用 {{ 变量名 }} 来接送 render() 方法传递的数据。
<h1> {{ n1 }} h1>
列表数据的传递和单个数据是一样的。区别在于接收时,索引不再使用方括号,而是使用点:
def index(request):
text=["Python", "C", "Java", "Basic"]
return render(request, 'index.html', {"n1": text})
<h1>计算机编程语言有 {{ n1.0 }},{{ n1.1 }},{{ n1.2 }},{{ n1.3 }} 等h1>
循环的语法如下:
{% for item in n1 %}
<span>{{ item }}span>
{% endfor %}
字典数据的传递是一样的。接收时,使用 变量名.键 来获取字典数据。
def index(request):
text ={"y1": "Python", "y2": "C", "y3": "Java", "y4": "Basic"}
return render(request, 'index.html', {"n1": text})
<h1>第一种语言是 {{ n1.y1 }},第二种语言是 {{ n1.y2 }},第三种语言是 {{ n1.y3 }},第四种语言是 {{ n1.y4 }}h1>
字典也可以使用循环
{% for k, v in n1.items %}
<li>{{ k }} = {{ v }} li>
{% endfor %}
对于嵌套的数据,例如列表里嵌套的字典,在接收时也可以使用嵌套的方式获取。类似于 python ,只是方括号改成了小数点。
判断语句语法如下:
{% if text == "Django老手" %}
<h1> 高手高手高高手 h1>
{% elif text == "初学 Django" %}
<h1> 菜鸟一个 h1>
{% else %}
<h1> 无法识别 h1>
{% endif %}
{{ 日期数据 | date:“Y-m-d H:i:s” }}
{{ bio | truncatewords:“30” }}
{{ my_list | first | upper }}
{{ name | lower }}
视图函数中有一个默认形参 request,它是一个对象,封装了用户发送过来的所有请求数据。 request 对象的常用属性和方法有:
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("user")
password = request.POST.get("pwd")
使用 HttpResponse 对象可以直接返回响应包,例如 HttpResponse(‘String’) 的响应包返回内容就是字符串。
使用 render() 方法通常返回一个 html 页面,也可以使用此方法向页面中传递数据。
redirect() 方法会重定向到另一个 url ,使用方法为:return redirect("https://www.baidu.com")
Django 开发操作数据库更简单,其内部提供了 ORM 框架,可以轻松的访问数据库。Django 官方支持 PostgreSQL、MariaDB、MySQL、Oracle、SQLite,还支持 MS SQL Server 等提供的后端。
django 数据库是基于其他的第三方库,所以需要先安装。 这里使用 MySQL 数据库。因为 django 对 pymysql 的支持不是很好,据说有错误,所以使用 mysqlclient 库。pip install mysqlclient
ORM 是 django 操作数据库的模块,可以创建、修改、删除数据库中的表(无法创建、删除数据库),还可以操作表中的数据。
ORM 连接数据库需要在 setting.py 中对 DATABASES 列表字段进行设置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'mytest', # 数据库名
'USER': 'root', # 访问数据库用户名
'PASSWORD': '123456', # 访问数据库用户的密码
'HOST': 'localhost', # 数据库主机
'PORT': 3306 # 访问数据库的端口
}
}
django 对于表的操作是在 models.py 文件中进行。
在此文件中,创建一个类,继承 models.Model 类,即可创建表。然后在内的成员中定义表结构。
class UserInfo(models.Model):
name = models.CharField(max_length=16, verbose_name='姓名')
password = models.CharField(max_length=64, verbose_name='密码')
age = models.IntegerField(verbose_name='年龄')
account = models.DecimalField(verbose_name='工资账户金额', max_digits=10, decimal_places=2, default=0) # 精准小数,最大位数10位,小数位2位,默认值为0
create_time = models.DateTimeField(verbose_name='入职时间')
depart = models.ForeignKey(to='Department', to_field='id', on_delete=models.CASCADE) # 外键,关联到 Department 表的 id 字段,级联删除
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
数据库中,表名称为app名称 + 下划线 + 小写类名。字段名称就是定义的成员变量,另外会自动添加一个名称为 id 的 bigint 型的自增字段作为主键。
常用的类型有:
常用的参数有:
# choices 选择约束
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
只需要在 models.py 文件中,将需要删除的表相对的类删除(或注释),即可删除表。同理删除字段就是在类定义中将相应字段定义的成员删除。
更改表或字段和删除表或字段类似,更改 models.py 文件中相应的类或类的成员即可。需要注意的是更改时数据库对数据的约束条件。例如没有设置可为空或具有默认值的字段,添加时要输入默认值等。
在终端执行命令
python manage.py makemigrations
就会读取并处理 models.py,如果第一次则会在 migrations 文件夹下建立索引为 1 的初始化文件 initial.py 。之后每次执行此命令会根据当时的 models.py 和 migrations 下的历史文件,对数据库表进行比较,创建结构变更记录文件。
然后在终端执行
python manage.py migrate
就会根据 migratrons 下最后的变更文件,对数据库相应表的结构进行变动。
另外需要注意的是,需要在 setting.py 文件中注册相应的 app。
在实际使用中,会在视图函数中进行数据操作,所以在 views.py 中需要引入 models.py 文件。
在视图函数中,可以使用表相应类的 create 方法添加数据
from app01 import models
# 添加数据,使用 models.表相应的类名称.objects.create(字段=值,字段=值...)
models.Department.objects.create(title='销售部')
models.UserInfo.objects.create(name='张三', password='123456', age=19)
可以使用 all 方法获取所有数据,得到的是 QuarySet 类型的对象,可以当成数据列表,列表中的每个元素都是一个数据对象可以使用 对象.字段 的方式获取数据。
data_list = models.UserInfo.objects.all()
for data in data_list:
print(data.id,data.name,data.password,data.age)
也可以使用 filter 方法进行条件筛选,但是筛选出来的数据哪怕只有一条,获取的也是 QuarySet 对象。不过可以使用 first 方法从 QuarySet 中获取第一条数据。
data = models.UserInfo.objects.filter(id=1).first()
对于查询条件,除了等于外还有
| 示例 | 说明 |
|---|---|
| id=12 | 数值字段id的值等于12 |
| id__gt=12 | 数值字段id的值大于12 |
| id__gte=12 | 数值字段id的值大于等于12 |
| id__lt=12 | 数值字段id的值小于12 |
| id__lte=12 | 数值字段id的值小于等于12 |
| name__startswith=“张” | 字符串字段name以 “张” 开始 |
| name__endswith=“三” | 字符串字段name以 “三” 结尾 |
| name__contains=“老” | 字符串字段name包含了字符串 “老” |
另外可以使用 exclude 方法来排除筛选条件
data = models.UserInfo.objects.filter(age=19).exclude(id=2).first()
使用 filter 方法和 all 方法返回的是一个查询的结果对象,是无法被 json 序列化的,有时候我们需要返回 json 结果,则可以使用 values 方法,参数可以指定返回的列。
row_dict = models.UserInfo.objects.filter(id=uid).values('id', 'name', 'password', 'age').first()
需注意的是,values 方法获取的是个字典列表,所以可以使用 first 方法获取第一个值。另外可以使用 values_list 方法获取元组列表,元组顺序为参数指定的列顺序。
row_dict = models.UserInfo.objects.filter(id=uid).values_list('id', 'name', 'password', 'age').first()
找到了需要处理的数据后,可以使用 delete 方法进行删除
models.Department.objects.all().delete()
models.UserInfo.objects.filter(id=3).delete()
类似于删除数据,可以使用 update 方法对数据进行更新
models.Department.objects.all().update(password=999) # 将所有数据的 password 字段值改为 999
models.UserInfo.objests.filter(id=3).update(name='张三',password='5445',age=20)
django 可以控制返回查询记录的起始索引和结束索引,有点类似于切片。即,使用 [ 起始记录索引号 : 结束记录索引号 ] 的方式,并且也遵循左闭右开的原则。
models.UserInfo.objects.all()[0:10] # 返回查询结果索引为 0 - 9 的记录
models.UserInfo.objects.filter(age__gte=22)[10:20] # 返回查询结果索引为 10 - 19 的记录
这样就可以进行分页了。起始记录索引号的值为 (页码 - 1) * 每页大小 , 结束记录索引号的值为 页码 * 每页大小。
可以使用 count 方法来返回查询记录总数
num = models.UserInfo.objects.filter(age__gt=19).count()
可以使用 aggregate 方法来进行一些复杂的聚合运算,例如求和
from django.db.models import Sum
num = models.UserInfo.objects.all().aggregate(nums=Sum('age'))
有些页面内容可以作为母版使用,在母版页面中填充子页面,可以提高代码复用,减少工作量。
在母版页中,需要更改内容(放置子页面)的地方,使用 block 进行标记,即制作好了母版页。
{% load static %}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>部门列表title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
head>
<body>
<script src="{% static 'js/jquery-3.6.0.min.js' %}">script>
<script src="{% static 'plugins/bootstrap-3.3.7-dist/js/bootstrap.min.js' %}">script>
{#导航条#}
<nav class="navbar navbar-default">
...
nav>
{#内容#}
<div>
{% block content %}{% endblock %}
div>
body>
html>
在子页面的第一行,写入要继承的母版页,然后将需要填充到母版页的代码同样使用 block 标记即可。
{% extends 'layout.html' %}
{% block content %}
{#内容#}
<div class="container">
<div class="panel panel-default">
...
div>
div>
{% endblock %}
form 表单的用法和 flask 等其他方法大致一样,不一样的地方在于 django 自带 csrf_token 校验。在视图函数获取表单数据时,会比对 csrf_token 的值,所以在 html 中的form 表单内部需要添加 {% csrf_token %} 用来生成随机值以进行比较。但是使用 form 表单会有很多问题,例如数据校验、错误提示、页面字段需要重写、关联表的数据呈现需要手动写入等等。所以在开发时通常使用 django 提供的组件来实现。
django 提供的 form 组件能够自动对数据进行校验,能够提供错误提示,能够自动生成字段。
使用 form 组件需要在 views.py 中创建一个继承自forms.Form的类,用来处理 form 的内容。
from django import forms
class MyForm(forms.Form):
# 定义 form 的字段,并使用插件
user = form.CharField(widget=forms.Input,label='用户名',required=True)
pwd = form.CharField(widget=forms.Input,label='密码',required=True)
email = form.CharField(widget=forms.Input)
然后在视图函数中实例化此类,并传递给前端页面
def index(request):
form = MyForm()
return render(request, 'index.html', {'form': form})
然后前端页面使用 form 对象创建表单,而不是使用 等标签创建。form 对象会自动生成插件所标识的 html 标签。
<form method="post">
{ form.user }}
{{ form.pwd }}
{{ form.email }}
-->
{% for field in form %}
{{ field.label }} : {{ field }}
{% endfor %}
form>
field.label 可以获取字段的 verbose_name 名称,如果没有定义则取字段名称。
使用 form 组件时,所有字段还是需要手动写一遍(在 views.py 定义的 form 类中),如果觉得麻烦,django 提供了另一个组件: ModelForm 组件。
使用 ModelForm 组件需要在 views.py 中创建一个继承自ModelForm的类,用来处理 form 的内容。
from django import forms
class MyForm(forms.ModelForm):
# 也可以使用自定义字段
# xx = form.CharField()
# 需要嵌套类 Meta
class Meta:
model = UserInfo # UserInfo 是 models.py 中定义的相应数据表的类
fields = ["name", "password", "age"] # 确定要操作的字段
# fields = "__all__" # 使用全部字段
# exclude = ["password"] # 排除特定字段,选择其他字段
# 使用自定义字段
# fields = ['name', 'password', 'age', 'xx']
传递数据和前端使用上则和 Form 组件类似
自动生成的标签会丢失样式,所以实际使用中,会在定义要操作的字段时,定义字段的插件及属性,在插件属性中可以添加样式。
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = UserInfo
fields = ["name", "password", "age"]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}), # 定义字段使用的插件
'password': forms.TextInput(attrs={'class': 'form-control'}) # 插件的参数中可以添加属性,将样式添加到这里
}
不过这样也字段多的时候也会比较麻烦。通常项目中会在 MyForm 初始化时,通过遍历字段给各字段添加属性的方式添加样式
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = UserInfo
fields = ["name", "password", "age"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
# 设置属性
if field.widget.attrs: # 如果字段中有属性,保留原属性
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = '请输入' + field.label
else:
field.widget.attrs = {'class': 'form-control', 'placeholder': '请输入' + field.label}
ModelForm 组件的表单时,可以将数据返回定义的类来进行校验等操作
form = MyForm(data=request.POST) # 创建对象,将提交的表单数据作为参数
if form.is_valid(): # 使用 is_valid 方法对数据进行有效性校验,如果有效返回 True
print(form.cleaned_data) # 整理数据成一个字典
form.save() # 保存数据
else:
# 如果校验失败,此时错误信息会保存在 form 里,可以将其返回页面,在页面中接收
return render(request, 'user_add_ModelForm.html', {'form': form})
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}label>
{{ field }}
<span style="color: red">{{ field.errors.0 }}span>
div>
{% endfor %}
如果需要在表单中填充数据,例如编辑的时候会有默认数据,只需要创建 form 对象时将数据库数据作为参数传给类的 instance 参数即可,其他的完全同生成表单
def user_edit(request, nid):
"""编辑用户"""
if request.method == "GET":
row_obj = models.UserInfo.objects.filter(id=nid).first()
form = MyForm(instance=row_obj)
return render(request, 'user_edit.html', {'form': form})
在提交表单时,使用 form.save() 方法可以添加数据到数据库,但是更新原有数据时必须告诉 form 操作的是哪条数据
row_obj = models.UserInfo.objects.filter(id=nid).first()
form = UserModelForm(data=request.POST,instance=row_obj)
if form.is_valid(): # 检测数据有效性
form.save() # 保存进数据库
return redirect('/user/list/')
else:
# 校验失败,显示错误信息
return render(request, 'user_add_ModelForm.html', {'form': form})
使用 form.save() 方法保存表单数据到数据库时,实际上保存的是用户提交的表单中的数据。如果有一些其他的数据不在用户提交的保单中,则可以手动添加数据到字段,然后保存
form = UserModelForm(data=request.POST)
if form.is_valid(): # 检测数据有效性
# 使用 form.instance.字段名 = 值 来手动添加数据,然后保存到数据库
form.instance.position = '新入职员工'
form.save() # 保存进数据库
return redirect('/user/list/')
else:
# 校验失败,显示错误信息
return render(request, 'user_add_ModelForm.html', {'form': form})
is_valid 方法默认进行空值检测,如果需要更多校验方式,例如字符位数等,需要自定义字段
from django import forms
class MyForm(forms.ModelForm):
name = forms.Charfield(min_length=3,label="用户名") # 此字段最小长度为3个字符
# name = forms.Charfield(disabled=True,label="用户名") # 不可用模式,但是提交表单时认为是空
password = forms.Charfield(label='密码',required=True) # 必填校验
class Meta:
model = models.UserInfo
fields = ['name', 'password', 'age']
在自定义字段时,可以添加参数 validators,使用正则表达式 RegexValidator 类进行校验自定义的字段。
from django.core.validators import RegexValidator
class PrettyNumForm(forms.ModelForm):
mobile = forms.CharField(
label='手机靓号',
validators=[RegexValidator(r'^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$','手机号格式错误')] # 参数2表示校验失败显示内容
)
class Meta:
model = models.PrettyNum
# fields = "__all__" # 使用所有字段
# exclude = ['level'] # 排除某个字段,取其他的字段
fields = ['mobile','price','level','status']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
if type(field.widget) == django.forms.widgets.TextInput or type(field.widget) == django.forms.widgets.NumberInput:
field.widget.attrs = {'class': 'form-control', 'placeholder': '请输入' + field.label}
else:
field.widget.attrs = {'class': 'form-control'}
可以在类定义中使用 clean_字段名 方法,将相应的字段数据传入并进行校验
def clean_mobile(self): # 校验 mobile 字段
from django.core.exceptions import ValidationError
txt_mobile = self.cleaned_data['mobile'] # 获取 mobile 字段的数据
if len(txt_mobile) != 11: # 进行检验
raise ValidationError("格式错误") # 没通过检验,抛出一个错误
else:
return txt_mobile # 通过检验,返回值
可以通过 form.cleaned_data 获取返回的 form 数据字典,所以可以通过 form.cleaned_data[key] 来获取需要的表单数据,来自定义一些数据校验方式。也可以使用 form.cleaned_data.pop(key) 的方式来获取特定键的值,区别在于使用 pop 方法为弹出数据。
如果自定义校验,或者进行数据核对(例如比较数据库中的信息),则可以主动添加错误信息并返回。
form = UserModelForm(data=request.POST)
if form.is_valid(): # 检测数据有效性
if models.UserInfo.objects.filter(name=form.cleaned_data['username'], password=form.cleaned_data['password']).exists():
# 登录成功,跳转页面
return redirect('/user/list/')
else:
# 用户名和密码对不上
form.add_error('password', '用户名或密码错误') # 主动添加错误信息给 password 字段
# 校验失败,显示错误信息
return render(request, 'user_add_ModelForm.html', {'form': form})
通过实例测试可以知道,返回的错误信息是英文的,这是因为 django 在设置上是使用英文 en-us ,可以在 setting.py 中的 LANGUAGE_CODE 设置成中文 zh-hans
有时候,在前端不好确定的代码,可以通过后端生成,然后传递给前端使用。这里以翻页的手动代码进行举例:
from django.utils.safestring import mark_safe # 将后端字符串标记为安全,可以传递到前端做为前端的 HTML 代码使用
# 前端的翻页页码
page_list = []
# 计算出当前页的前三后三页
if page >= 5: # 需要首页
ele = f'- {q}
&order={order}&by={by}&page=1">首页'
page_list.append(ele)
for i in range(page - 3, page + 4):
if i <= 0 or i > all_page:
continue
if i == page:
ele = f'- {q}
&order={order}&by={by}&page={i}">{i}(current)'
else:
ele = f'- {q}
&order={order}&by={by}&page={i}">{i}'
page_list.append(ele)
if page <= all_page - 4:
ele = f'- {q}
&order={order}&by={by}&page={all_page}">尾页'
page_list.append(ele)
page_str = mark_safe(''.join(page_list)) # 使用 mark_safe() 方法将字符串变为前台代码
return render(request,'pn_list.html', page_str)
如果不使用 mark_safe 标记,传到前端的数据会以字符串形式呈现。标记后则前端认为字符串是就是前端代码。
在 django 中,使用 request.session[key] = value 能够记录 session 的信息,同时会自动生成验证 session_id 返回到用户浏览器 cookie 中,并且将 session_id 和验证字符串都存储到数据库的 django_session 表中,以便下次使用时自动验证。
如果是已经记录 session 信息的用户再次访问,则请求信息的 cookie 中会有 session_id 。django 会从数据库中查询是否存在此 session_id,如果存在则能够获取设置的 session[key] 的 value ,如果不存在则不能够获取。所以使用 request.session.get(key) 来获取设置数据就能够自动检查 session 信息。
可以使用 request.session.set_expiry(sec) 来设置 session 的超时时间,sec 单位为秒。
使用 request.session.clear() 方法可以清除 session 信息,即进行注销。
django 收到一个请求后,会经过一系列的中间件后达到视图函数,经过视图函数处理后,再经过这些中间件后将数据返回给用户浏览器。
中间件处理请求的方法是 process_request,处理响应的方法是 process_response。如果请求在通过中间件时, process_request 没有返回值(或返回值为 None),则会继续交给下一步处理;如果有返回值(返回值是 HttpResponse、render、redirect),则会禁止请求往下进行,而将返回值交给本类的 process_response 方法返回请求,直接返回给客户端,所以可以将一些放在视图函数之前或之后的行为写在中间件中。
中间件的处理顺序按照在 settings.py 中注册的顺序,请求是顺序处理,响应是逆序处理。
django 使用的中间件可以在 settings.py 中的 MIDDLEWARE 字段定义。
所有自定义的中间件(类)需继承 django.utils.deprecation.MiddlewareMixin 类。并且要在 settings.py 中添加注册
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class AuthMiddleware(MiddlewareMixin):
"""访问验证中间件"""
def process_request(self,request):
# 如果请求访问 login 页面则不进行检测
if request.path_info == '/login/': # request.path_info 能获取请求URL
return
# 1. 读取当前访问用户的 session 信息,如果能读到,说明已经登录过
username = request.sesssion.get('username')
if username:
return
# 2. 如果没有登录过,
else:
return redirect('/login/')
def process_response(self,request,response):
return response
django 项目中也可以使用 ajax 技术
前端和其他 web 框架一样,需要注意的是,django 接收 POST 请求时,需要 csrf_token 进行验证,而在 ajax 中获取 csrf_token 比较麻烦。所以通常会在后端免除 csrf_token 验证。
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<h1>任务管理h1>
<input type="button" class="btn btn-primary" value="点击" onclick="clickMe();"/>
div>
{% endblock %}
{% block js %}
<script type="text/javascript">
function clickMe() {
$.ajax({
url: "/test/ajax/",
type: "get",
data: {
type:'add',
n1:123,
n2:456
},
success: function (res) {
console.log(res);
}
})
}
script>
{% endblock %}
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<h1>任务管理h1>
<input type="button" class="btn btn-primary" value="点击" id="btn1" />
div>
{% endblock %}
{% block js %}
<script type="text/javascript">
$(function (){
// 页面框架加载完成后代码自动执行
bindBtn1Event(); // 绑定事件
})
function bindBtn1Event(){
$("#btn1").click(function (){ // 绑定到 btn1 的 click 事件的函数
$.ajax({
url: "/task/ajax/",
type: "POST",
data: {
type:'add',
n1:123,
n2:456
},
dataType:'JSON',
success: function (res) {
console.log(res);
// console.log(res.res_add)
// console.log(res['res_add'])
}
})
})
}
script>
{% endblock %}
后端部分只要将响应请求 url 绑定视图函数,则可以在视图函数中进行处理
def task_ajax(request):
"""测试ajax"""
return HttpResponse('成功')
可以使用 json 的 dumps 方法将字典转为 json 并返回
import json
def task_ajax(request):
res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}
res_dict = json.dumps(res_dict)
return HttpResponse(res_dict)
也可以直接使用 JsonResponse 返回数据
from django.http import JsonResponse
def task_ajax(request):
res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}
return JsonResponse(res_dict)
前端发送 post 请求时如果不加载 csrf_token,则后端需要禁用 csrf 校验检查
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def task_ajax(request):
return HttpResponse('成功')
Ajax 结合 ModelForm 在前端实际上几乎没有改变,要注意的也就是按钮绑定 Ajax 函数和不用 csrf_token。
在后端,ModelForm 其实接收到的数据也是 form 的字典格式,包含校验、保存等处理方式没有变化,只是在返回值时有了变化。
因为 Ajax 是接收处理数据,所以后端返回 重定位 信息是无法跳转的。如果希望跳转,则需要返回一个 json ,ajax 收到了这个特定的 json 数据后使用 js 进行页面跳转。
另外返回 ModelForm 错误信息时,form.errors 获取的是一个字典,可以整理成 json 格式返回给 ajax 处理。
<form id="addForm" novalidate>
<div class="clearfix">
{% for field in form %}
<div class="col-xs-6">
<div class="form-group">
<label>{{ field.label }}label>
{{ field }}
{ field.errors.0 }} -->
<span style="color: red">span>
div>
div>
{% endfor %}
<div class="col-xs-12">
<button type="button" id="btnAdd" class="btn btn-primary">添加button>
div>
div>
form>
<script type="text/javascript">
$(function () {
// 页面框架加载完成后代码自动执行
bindBtnAddEvent();
})
function bindBtnAddEvent() {
$("#btnAdd").click(function () {
$(".error-msg").empty(); // 清空上一次的错误信息
$.ajax({
url: "/task/add/",
type: "POST",
data: $("#addForm").serialize(),
dataType: 'JSON',
success: function (res) {
if(res.status){
alert("添加成功!");
}else{
console.log(res);
$.each(res.error,function (name,data){ // 循环每一个错误,获取键值
// 拼接 id_ 和 name,获取对应文本框的 id
// 查找文本框元素的下一个元素(span),使其文本(text)为错误信息
$("#id_" + name).next().text(data[0]);
})
}
}
})
})
}
script>
前端会将文件以 post 请求发送至后端,后端可以使用 request.FILES 来接收。需要注意的是 form 需要 enctype 属性。
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="text" name="filename">
<input type="file" name="file">
<input type="submit" value="提交">
form>
file_obj = request.FILES.get('file') # 获取上传文件的对象
filename = request.POST.get('filename', file_obj.name) # 获取设置的文件名,如果没有则为原文件名。
with open(filename, mode='wb') as f:
for chunk in file_obj.chunks(): # 将上传文件分块读取
f.write(chunk)
使用 Form 时可以定义文件字段 FileField ,定义后就能够上传文件了。
class UpForm(forms.Form):
name = forms.CharField(label='姓名')
age = forms.IntegerField(label='年龄')
img = forms.FileField(label='头像')
def upload_form(request):
form = UpForm(data=request.POST, files=request.FILES)
if form.is_valid():
print(form.cleaned_data)
# 文件在 form.cleaned_data 的相应字段(img)内
img = form.cleaned_data.get('img')
file_path = os.path.join('app01', 'static', 'img', img.name) # 拼接文件路径
with open(file_path,'wb') as f: # 写入文件
for chunk in img.chunks():
f.write(chunk)
else:
return render(request, 'upload.html', {'form': form})
<form method="post" enctype="multipart/form-data" novalidate>
.
.
.
form>
通常使用的静态资源都在 static 文件夹内,如果想要用户上传文件到一个特定文件夹,例如项目根目录下的 media 文件夹,则需要在 urls.py 中进行设置启用。首先引入一些资源,再在 urls.py 文件的 urlpatterns 字段添加:
from django.views.static import serve
from django.urls import path, re_path
from django.conf import settings
re_path(r'^media/(?P.*)$' , serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
然后在 settings.py 中进行配置:
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 项目根目录下的 media 目录
MEDIA_URL = '/media/'
此时上传文件保存的路径可以写为
media_file_path = os.path.join('media', image_obj.name)
这样将用户上传的文件放在 media 目录下,也可以通过 /media/文件名 的 url 来访问,例如 http://127.0.0.1:8000/media/001.png
ModelForm 组件可以自动上传文件,并存储保存路径到数据库,不用再写相应保存的代码,且能够自动进行重名处理。只是需要设置了上传保存目录。
需注意的是,保存到数据库中的文件路径也是 media 下的相对路径,并且不包含 media ,使用时候注意添加 media。
# models.py
class City(models.Model):
"""城市"""
name = models.CharField(verbose_name='名称', max_length=32)
count = models.IntegerField(verbose_name='人口')
# 本质上数据库存储的是文件路径,也是CharField,可以自动保存数据
# upload_to 指的就是上传文件到哪个目录,是 media 目录下的相对路径
img = models.FileField(verbose_name='logo', max_length=128, upload_to='city/')
# views.py
class UpModelForm(forms.ModelForm)
class Meta:
model = models.City
fields = '__all__'
def upload_modal_form(request):
form = UpModelForm(data=request.POST, files=request.FIELS)
if form.is_valid():
# 保存文件,保存 form 字段信息和文件路径并写入数据库
form.save()
return HttpResponse('成功')