Django组件¶
1. 文件上传处理¶
- 当Django在处理文件上传的时候,文件数据被保存在request.FILES
- FILES中的每个键为
<input type="file" name="字段名" />中的name - 注意:FILES只有在请求的方法为POST 且提交的
<form>带有enctype="multipart/form-data" 的情况下才会包含文件。否则requests.FILES 将为一个空的类似于字典的对象 - 使用模型处理上传文件:将属性定义成models.ImageField或者models.FileField类型
from django.db import models
# Create your models here.
class Software(models.Model):
name = models.CharField(max_length=150, verbose_name="软件名称")
version = models.CharField(max_length=50, verbose_name="版本号")
website = models.URLField(max_length=500, verbose_name="官方网址")
# upload_to 用于设置保存上传文件的存储子路径,跟着settings.py中存储上传文件配置项MEDIA_ROOT的后部分路径
# ImageField是FileField的子类,FileField内部实现了基于日期时间格式生成目录的功能,所以支持使用%日期符号来自动创建目录的。
# 而且,当同一目录下文件同名了,FileField会自动把后面重复的文件名追加补充随机字符串防止重名。
picture = models.ImageField(upload_to="soft/%Y/%m/%d/", verbose_name="缩略图")
downloads = models.FileField(max_length=500, upload_to="attr/%Y/%m/%d/", verbose_name="下载地址")
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
class Meta:
db_table = "tb_software"
verbose_name = "应用管理管理"
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.name}.{self.version}"
- 注意:如果属性的字段类型为图片类型ImageField,需要在python安装PIL包
图片等文件进行上传处理时,还要在django的配置文件中设置存储文件的根路径才可以
- 在项目根目录下创建保存上传文件的根文件夹,例如:uploads
- 图片上传后,会被保存到上面创建的上传文件存储根文件夹下
- 在settings.py配置文件中,通过MEDIA_ROOT配置项来设置这个根文件夹,让django的上传文件处理类能识别到
使用postman,完成文件上传,或者使用模板引擎或前端代码,实现一个上传文件的表单
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<form method="post" action="http://127.0.0.1:8000/component/soft/" enctype="multipart/form-data">
{% csrf_token %}
软件名:<input type="text" name="name"><br><br>
版本号:<input type="text" name="version"><br><br>
官方网址:<input type="text" name="website"><br><br>
版本号:<input type="text" name="version"><br><br>
软件缩略图:<input type="file" name="picture"/><br><br>
软件上传:<input type="file" name="downloads"/><br><br>
<input type="submit" value="提交">
</form>
</body>
</html>
视图处理上传文件的代码,views.py:
import os
from django.views import View
from django.http.response import JsonResponse
from . import models
# Create your views here.
class SoftWareView(View):
def post(self, request):
"""添加数据"""
# 接受来自客户端的请求数据
name = request.POST.get("name")
version = request.POST.get("version")
website = request.POST.get("website")
picture = request.FILES.get("picture")
downloads = request.FILES.get("downloads")
software = models.Software.objects.create(
name=name,
version=version,
website=website,
picture=picture,
downloads=downloads
)
return JsonResponse({
"id": software.id,
"name": software.name,
"version": software.version,
"website": software.website,
# 模型字段是图片或者文件的,当前属性是文件对象,是无法直接被json序列化的,所以调用url属性才能获取url访问路径
"picture": f"//{request.META.get('HTTP_HOST')}{software.picture.url}",
"downloads": f"//{request.headers.get('host')}{software.downloads.url}",
})
def delete(self, request):
"""删除操作"""
id = request.GET.get("id")
software = models.Software.objects.filter(pk=id).first()
# 通过path可以获取当前上传的绝对路径,通过绝对路径可以删除文件
print(software.picture.path)
# 删除操作代码:
os.remove(software.picture.path)
os.remove(software.downloads.path)
return JsonResponse({})
urls.py,代码:
from django.urls import path
from . import views
urlpatterns = [
path("soft/", views.SoftWareView.as_view()),
]
效果:
1.1 上传文件提供外界访问¶
settings.py,代码:
# 项目中存储上传文件的根目录[手动创建],注意,uploads目录需要手动创建否则上传文件时报错
MEDIA_ROOT = BASE_DIR / "uploads"
# 访问上传文件的url地址前缀
MEDIA_URL = "/uploads/"
urls.py,代码:
from django.views.static import serve # 静态文件代理访问模块
urlpatterns = [
re_path(r'uploads/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
]
2. 数据分页¶
分页的本质,实际上就是SQL语句的限制结果而已。
select * from tb_student
# 能查尽查,默认查询全部数据。但是我们用户看不了也不会去看太多数据的。
# 而SQL语句有可能查询出几十万条数据,这是没必要的,所以我们可以采用limit 配合 offset来进行分页查询
select * from db_student limit 10 offset 0; # 第1页数据,limit 0,10;
select * from db_student limit 10 offset 10; # 第2页数据,limit 10,10;
select * from db_student limit 10 offset 20; # 第3页数据,limit 20,10;
select * from db_student limit <size> offset <start>; # 第n页数据,limit (n-1)*size, size;
Django提供了一些类实现管理数据分页,这些类位于django/core/paginator.py中。
注意:
django提供的这些分页相关的类,默认是用于前后端不分离的。
2.1 Paginator对象¶
- Paginator(数据对象列表,int):返回分页器对象,参数1为要进行分页的列表数据,每一页数据的条数(Limit)
2.1.1 属性¶
- count:数据对象总数,就是 len(数据对象列表) 的结果
- num_pages:页码总数,就会 count/ Limit的结果
- page_range:页码列表,从1开始,例如[1, 2, 3, 4]
2.1.2 方法¶
- page(num):创建Page对象,参数num代表页码,以1开始,如果提供的页码不存在,抛出InvalidPage异常
2.2.2 异常exception¶
- InvalidPage:当向page()传入一个无效的页码时抛出
- PageNotAnInteger:当向page()传入一个不是整数的值时抛出
- EmptyPage:当向page()提供一个有效值,但是那个页面上没有任何对象时抛出
2.2 Page对象¶
页码对象。
2.2.1 创建对象¶
- Paginator对象的page()方法返回Page对象,不需要手动构建。
2.2.2 属性¶
- object_list:当前页要显示给外界的所有数据对象的列表
- number:当前页码,从1开始
- paginator:当前Page对象相关的Paginator对象
2.2.2 方法¶
- has_next():如果有下一页返回True
- has_previous():如果有上一页返回True
- hasotherpages():如果有上一页或下一页返回True
- nextpagenumber():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
- previouspagenumber():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
- len():返回当前页面对象的个数
- 迭代页面对象:访问当前页面中的每个对象
2.3 代码示例¶
先设置setttings.py中的TEMPLATE模板引擎的模板目录,代码:
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",
],
},
},
]
models.py,代码:
from django.db import models
class Student(models.Model):
STATUS = (
(0, "正常入学"),
(1, "正常毕业"),
(2, "已经辍学"),
)
name = models.CharField(max_length=15, verbose_name="学生名字")
age = models.SmallIntegerField(verbose_name="年龄")
sex = models.BooleanField(default=True, verbose_name="性别")
classmate = models.CharField(db_column="class", max_length=50, verbose_name="班级")
mobile = models.CharField(max_length=20, unique=True, verbose_name="下载地址")
description = models.TextField(null=True, verbose_name="个性签名")
status = models.SmallIntegerField(null=True, verbose_name="状态码")
created_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
class Meta:
db_table = "student"
verbose_name = "学生信息"
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.name}"
views.py,代码:
from django.shortcuts import render
from django.views import View
from django.core.paginator import Paginator
from . import models
class StudentView(View):
def get(self, request):
"""数据分页"""
"""提供了数据对象列表以及单页数据量,创建分页器对象"""
# Paginator(数据对象列表, limit)
student_list = models.Student.objects.all()
paginator = Paginator(list(student_list), 10)
# # 数据列表的长度
# print(paginator.count)
# # 页码总数
# print(paginator.num_pages)
# # 页面列表
# print(paginator.page_range)
"""基于分页器对象,创建分页对象"""
# 接受客户端的页码,页面一般都是查询字符串,或者路径参数
current_page = request.GET.get("page",1)
page = paginator.page(current_page)
# # 当前页要展示给外界的数据对象列表
# print(page.object_list)
# # 当前页码
# print(page.number)
# # 逆向查找当前Page分页对象的父级分页器对象
# print(page.paginator)
return render(request, "index.html", locals())
urls.py,代码:
from django.urls import path
from . import views
urlpatterns = [
path("student/", views.StudentView.as_view()),
]
list.html,模板代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页</title>
<style>
.table-data{
border-collapse: collapse;
width: 680px;
}
.table-data td, th{
border: 1px solid red;
font-weight: normal;
text-align: center;
}
.paginator{
width: 680px;
text-align: center;
padding-top: 10px;
}
.paginator a{
color: #000;
text-decoration: none;
border: 1px solid lightsalmon;
padding: 4px 8px;
cursor: pointer;
border-radius: 4px;
}
.paginator a.current,
.paginator a:hover{
background: lightsalmon;
color: wheat;
}
</style>
</head>
<body>
<table class="table-data">
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
<th>Sex</th>
<th>Class</th>
<th>Mobile</th>
</tr>
{% for student in page.object_list %}
<tr>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.age }}</td>
<td>{{ student.sex }}</td>
<td>{{ student.classmate }}</td>
<td>{{ student.mobile }}</td>
</tr>
{% endfor %}
</table>
<div class="paginator">
<a href="?page=1">首页</a>
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">上一页</a>
<a href="?page={{ page.previous_page_number }}">{{ page.previous_page_number }}</a>
{% endif %}
<a>{{ page.number }}</a>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">{{ page.next_page_number }}</a>
<a href="?page={{ page.next_page_number }}">下一页</a>
{% endif %}
<a href="?page={{ page.paginator.num_pages }}">尾页</a>
</div>
</body>
</html>
2.4 ListView分页¶
django为了方便开发者快速的实现业务功能,针对简单视图数据的管理操作,例如增删查改操作,都有提供了封装视图类给开发者使用,在django.views.generic中定义了所有的封装视图类:ListView,CreateView,UpdateView,DetailView,DeleteView。
注意:
所有的封装视图类,都是View类的子类,所以View类中提供的http请求,也可以在封装视图类中使用,封装视图要绑定路由,一样需要使用as_view来绑定。
| 封装视图类 | 作用 | 补充 |
|---|---|---|
| ListView | 显示列表数据 | 支持分页,提供get方法 |
| CreateView | 显示添加数据表单和数据添加功能 | 提供表单,提供get、post方法 |
| UpdateView | 显示更新数据表单和数据更新功能 | 提供表单,提供get、put、patch方法 |
| DetailView | 显示详情数据 | 只显示一条数据,提供get方法 |
| DeleteView | 删除一条数据 | 提供delete方法 |
视图代码:
from django.shortcuts import render
from django.views import View
from django.core.paginator import Paginator
from . import models
class StudentView(View):
def get(self, request):
"""数据分页"""
"""提供了数据对象列表以及单页数据量,创建分页器对象"""
# Paginator(数据对象列表, limit)
student_list = models.Student.objects.all()
paginator = Paginator(list(student_list), 10)
# # 数据列表的长度
# print(paginator.count)
# # 页码总数
# print(paginator.num_pages)
# # 页面列表
# print(paginator.page_range)
"""基于分页器对象,创建分页对象"""
# 接受客户端的页码,页面一般都是查询字符串,或者路径参数
current_page = request.GET.get("page",1)
page = paginator.page(current_page)
# # 当前页要展示给外界的数据对象列表
# print(page.object_list)
# # 当前页码
# print(page.number)
# # 逆向查找当前Page分页对象的父级分页器对象
# print(page.paginator)
return render(request, "index.html", locals())
from django.views.generic import ListView
class Student2View(ListView):
# # 设置当前视图提供哪些方法,默认支持get
# http_method_names = ["get"]
# 设置当前视图类中使用模板文件名
template_name = "index2.html"
# 设置当前视图类中使用的模型
model = models.Student
# 设置分页的数据量
paginate_by = 5
# # 设置分页的页码,默认是"page"
# page_kwarg = "page"
# 在HTML模板中,代表page对象的object_list变量名
# context_object_name = "student_list"
urls,代码:
from django.urls import path
from . import views
urlpatterns = [
path("student/", views.StudentView.as_view()),
path("student2/", views.Student2View.as_view()),
]
index2.html,模板代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页</title>
<style>
.table-data{
border-collapse: collapse;
width: 680px;
}
.table-data td, th{
border: 1px solid red;
font-weight: normal;
text-align: center;
}
.paginator{
width: 680px;
text-align: center;
padding-top: 10px;
}
.paginator a{
color: #000;
text-decoration: none;
border: 1px solid lightsalmon;
padding: 4px 8px;
cursor: pointer;
border-radius: 4px;
}
.paginator a.current,
.paginator a:hover{
background: lightsalmon;
color: wheat;
}
</style>
</head>
<body>
<table class="table-data">
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
<th>Sex</th>
<th>Class</th>
<th>Mobile</th>
</tr>
{% for student in page_obj.object_list %}
<tr>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ student.age }}</td>
<td>{{ student.sex }}</td>
<td>{{ student.classmate }}</td>
<td>{{ student.mobile }}</td>
</tr>
{% endfor %}
</table>
<div class="paginator">
<a href="?page=1">首页</a>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
<a href="?page={{ page_obj.previous_page_number }}">{{ page_obj.previous_page_number }}</a>
{% endif %}
<a>{{ page_obj.number }}</a>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">{{ page_obj.next_page_number }}</a>
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
{% endif %}
<a href="?page={{ page_obj.paginator.num_pages }}">尾页</a>
</div>
</body>
</html>
3. 缓存 Cache¶
- 缓存就是一种优化网站性能的方案,是一个利刃,用的好可以给服务器大量降压,用得不好,反而成为压垮骆驼的最后一根草。
- 所谓的缓存,实际上往往是把数据库中数据提取出来,并临时存放到一个第三方存储介质中,例如:内存/文件中。
-
一般常见的缓存存储介质:内存(redis/memcached/mysql的内存表),硬盘文件中(python,json,二进制文件,html),CDN(内容分发网络,静态文件)
-
对于中等流量的网站来说,尽可能地减少开销是必要的。缓存数据就是为了保存那些需要很多计算资源的结果,这样的话就不必在下次重复消耗计算资源
-
Django自带了一个健壮的缓存系统来保存动态页面,避免对于每次请求都重新计算
-
Django提供了不同级别的缓存粒度:可以缓存特定视图的输出、可以仅仅缓存那些很难生产出来的部分、或者可以缓存整个网站
-
视图缓存
- 模板缓存
- 缓存API
注意i,这3个缓存级别不能同时使用。
3.1 设置缓存¶
- 通过设置settings.py中的CACHES配置项决定把数据缓存在哪里,是数据库中、文件系统还是在内存中
- 参数TIMEOUT:缓存的默认过期时间,以秒为单位,这个参数默认是300秒,即5分钟;设置TIMEOUT为None表示永远不会过期,值设置成0造成缓存立即失效
# Cache
# https://docs.djangoproject.com/zh-hans/4.2/topics/cache/
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:16379",
"TIMEOUT": 60,
}
}
- 可以将cache存到redis中,默认采用0号数据库,此处需要保证我们学习的机子上已经预装redis了。
- 在python中连接并操作redis,需要安装django-redis-cache包并配置如下:
settings.py,代码:
CACHES = {
"default": {
"BACKEND": "redis_cache.cache.RedisCache", # django-redis-cache
"LOCATION": "redis://127.0.0.1:6379", # 数据源格式连接写法 mysql://账号:密码@IP:端口/数据库名称?
'TIMEOUT': 60, # 缓存失效时间,这里60秒只是为了测试而已
},
}
- 可以连接redis查看存的数据
# 终端下连接redis,进行redis交互终端:
redis-cli # memurai-cli
# 切换数据库:
select 15
select 0
# 设置一个数据,保存到指定变量名中 set 变量 值
set name xiaoming
# 查看数据的变量(键/key):
keys *
# 获取指定变量名的数据:
get name
3.2 视图缓存¶
- django.views.decorators.cache定义了cache_page装饰器,用于对视图的输出进行缓存
- 示例代码如下:
"""缓存函数视图的返回结果"""
from django.http.response import HttpResponse
from django.views.decorators.cache import cache_page
import random
@cache_page(timeout=60 * 60 * 20 + random.randint(1, 9999)) # 一为了避免所有视图缓存同一时间过期,造成服务器需要在短时间内生成大量的缓存,一般是设置随机数过期时间。
def index(request):
print("执行视图代码了!")
return HttpResponse('hello!')
"""缓存类视图的返回结果"""
# cache_page 是基于函数视图进行缓存的,所以无法直接给类视图使用,需要使用method_decorator进行类视图转换
from django.utils.decorators import method_decorator
class IndexView(View):
@method_decorator(cache_page(timeout=60))
def get(self, request):
print("执行视图代码了!")
return HttpResponse('hello! IndexView.get')
- cache_page接受一个参数:timeout,秒为单位
- 视图缓存与URL无关,如果多个URL指向同一视图,每个URL将会分别缓存
urls.py,代码:
from django.urls import path
from . import views
urlpatterns = [
path("student/", views.StudentView.as_view()),
path("student2/", views.Student2View.as_view()),
path("index/", views.index),
path("index2/", views.IndexView.as_view()),
]
上面的视图缓存,是直接在中间件层面拦截的,当视图缓存存在时,直接在中间件就返回结果了。而开发中经常存在一个视图中提供多个数据,其中只有部分数据是缓存的,其他需要实时从数据库中读取的。这种情况就不适合使用视图缓存,而应该使用粒度更小的缓存方式-缓存API。
3.3 缓存API¶
缓存API是针对于某个变量数据进行单独缓存的,使用上比视图缓存更为灵活。
from django.core.cache import cache
设置:cache.set(键,值,有效时间)
获取:cache.get(键)
删除:cache.delete(键)
清空:cache.clear() # 慎用,这个会把整个库所有的 缓存数据全部清空!
视图,代码:
"""Django提供的缓存对象进行数据缓存[缓存API]"""
from django.http.response import JsonResponse
from django.core.cache import cache
class HomeView(View):
def get(self, request):
# 读取缓存
student_list = cache.get("student_list")
# 判断缓存结果,如果没有,则读取数据库并写入缓存
if not student_list:
print("读取数据库!")
student_list = list(models.Student.objects.values_list())
cache.set("student_list", student_list, 10) # 这里的10表示当前数据缓存10秒后过期,当然在实际工作中,不会设置10秒的。
return JsonResponse(student_list, safe=False)
def delete(self,request):
"""删除/更新数据时,记得要删除缓存哦"""
# 删除/更新数据时,先删缓存,再删除/更新数据库的数据
cache.delete("student_list")
return JsonResponse({}, safe=False)
urls.py,代码:
from django.urls import path
from . import views
urlpatterns = [
path("student/", views.StudentView.as_view()),
path("student2/", views.Student2View.as_view()),
path("index/", views.index),
path("index2/", views.IndexView.as_view()),
path("home/", views.HomeView.as_view()),
]
缓存适用于哪些场景?不适用于哪些场景?
适用于数据稳定,不会经常发生变化的业务中,例如:配置信息,文章、新闻、商品等展示数据。
不适用于实时性要求比较高的业务中,例如:股市k线图,实时直播的新闻、聊天....
