跳转至

Django组件

add_circle2025-03-04update2025-03-05

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包
pip install Pillow

# 把上面User模型进行数据迁移
python manage.py makemigrations
python manage.py migrate

图片等文件进行上传处理时,还要在django的配置文件中设置存储文件的根路径才可以

  • 在项目根目录下创建保存上传文件的根文件夹,例如:uploads
  • 图片上传后,会被保存到上面创建的上传文件存储根文件夹下
  • 在settings.py配置文件中,通过MEDIA_ROOT配置项来设置这个根文件夹,让django的上传文件处理类能识别到
# 设置保存上传文件的公共路径
MEDIA_ROOT = BASE_DIR / "uploads"

使用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()),
]

效果:

image-20230927144456251

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包并配置如下:
# pip install cryptography  # 这个是连接数据库有时候涉及加密时使用的模块
pip install 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线图,实时直播的新闻、聊天....