Skip to content

Django进阶学习笔记

admin

注册自定义模型类

若要自己定义地模型类也能在/admin后台管理界面中显示和管理,需要将自己的类注册到后台哦管理界面

python
# admin.py
from .models import Book
admin.site.register(自定义模型类)

模型类的名字是你定义模型的__str__返回值

模型管理器类

作用:为后台管理界面添加便于操作的新功能

说明:后台管理器类须继承自django.contrib.admin里面的ModelAdmin类

python
from django.contrib import admin
from .models import *

class XXXManager(admin.ModelAdmin):
    ''''''
    pass

admin.site.register(YYY, XXXManager)  # 绑定YYY模型
# 1.这里也注册了模型类
# 2.绑定了管理类

字段:

  • list_display = ['id', 'title', 'content', ......] : 作用是把该域内的字段也显示在管理界面中,否则管理界面中就只会显示模型的类名

    image-20211101215931433

    verpose_name就是控制这上面的字段名的

  • list_display_links = ['title'] : 作用就是把超链接放在你指定的字段名上,这里造成的变化就是把上面id列的蓝色转移到书名列使其变为蓝色

  • list_filter = ['pub'] : 作用就是对某个字段进行过滤显示的作用

  • search_fields = ['title'] : 以某个字段添加搜索框,模糊查询

  • list_editable = ['price'] : 添加可在列表编辑的字段

image-20211101220946145

ORM查询系统

  • all()

  • values('列1', ‘列2’)

    查询部分列的数据返回

  • filter()

    • python
      MyModel.objects.filter(属性1=值1, 属性2=只)
    • 作用:返回包含此条件的全部数据集

    • 当多个属性在一起时默认为'与'关系

  • exclude(): 不包含

  • 条件查询--查询谓词

    • __exact:等值匹配,一般不加,默认就为这个,匹配null时会加

    • __contains:包含指定值

    • __startswith:

    • __endswith:

    • __gt:大于

    • __gte:大于等于

    • __lt:小于

    • __lte:小于等于

    • __in:查找数据是否在指定范围内

      python
      Auther.objects.filter(country__in=['中国', '日本', '韩国'])
    • __range:查找数据是否在指定的区间范围内

      python
      Auther.objects.filter(age__range=(35,50))
  • F对象

    • 一个F对象代表数据库中某条记录的字段的信息

    • 通常是对数据库中的字段值在不获取的情况下进行操作用于类属性之间的比较

    • python
      from django.db,models import F
      F('列名')
    • python
      # 示例:更新Book实例中所有的零售价涨10元
      Book.objects.all().update(market_price=F('market_pice')+10)
      
      # 以上做法好于如下代码
      books = Book.objects.all()
      for book in books:
          book.market_price=book.marget_price+10
          book.save()

      上面的做法相当于+=10,而下面的做法就是把这个值实实在在地查出来之后加上10,再把这个最终的值放进去

      解决并发计数的问题

      比如点赞数的更新就必须是F对象操作,而不能将数据库中的值取出来,否则可能造成多端不同步的情况

  • Q对象

    • 当在获取查询结果集,使用复杂的逻辑或、逻辑非,与非(~&)等操作时可以借助Q对象进行操作

    • 如:想找出定价低于20元或清华大学出版社的全部书,可以写成

      python
      Book.objects.filter(Q(price__lt=20)|Q(pub="清华大学出版社"))
  • 聚合函数

    • python
      from django.db.models import *
    • Sum, Avg, Count, Max, Min

    • python
      MyModel.objects.agregate(结果变量名=聚合函数('列'))
      return {"结果变量名": 值}
    • 在后面加filter就等于having

  • 原生数据库操作

    python
    Mymodel.bjects.raw(sql语句, 拼接参数)
    books = models.Book.objects.raw('select * from bookstore_book')

    使用原生数据库操作时需要小心SQL注入

    定义:用户通过数据上传,将恶意的sql语句提交给服务器,从而达到攻击效果

    案例1:用户在搜索好友的表单框里输入‘1 or 1=1’

    python
    s1 = Book.objects.raw('select * from bookstore_book where id=%s'%('1 or 1=1'))

    攻击结果:查询出所有用户

    解决办法:使用拼接参数

    cursor

Cookies与Session

cookies特点

  • cookies在浏览器上是以键值对的形式进行存储的,键和值都是以ASCII字符串的形式存储(不能是中文字符)
  • 存储的数据带有生命周期
  • cookies中的数据是按域存储隔离的,不同的域之间无法访问
  • cookies内部的数据会在每次访问此网址时都会携带到服务器端,如果cookies过大会降低响应速度

存储

python
HttpResponse.set_cookie(key, value="", max_age=None, expire=None)
'''
key: cookies的名字
value: 值
max_age: 存活时间,秒为单位
expires: 具体过期时间
注:当不指定后两个参数的时候,关闭浏览器时此数据失效
'''

删除

python
HttpResponse.delete_cookie(key)
# 删除指定key的cookie,如果key不存在则什么也不发生

获取

python
value = request.COOKIES.get('cookies名', '默认值')
# 其中request.COOKIES返回的时字典

Session

原因:数据都存在了浏览器端,每次请求都要传一大堆,同时也不安全

解决:浏览器端仅仅保存一段身份验证码,其他的数据全部存在服务器端

session是在服务器上开辟的一段空间用于保留浏览器和服务器交互时的重要数据

实现方式:

  • 使用sesion需要在浏览器客户端启动cooke,且在cookie中存储sessionId
  • 每个客户端都可以在服务器端有一个独立的session
  • 注意:不同的请求者之间不会共享这个数据,与请求者一一对应

配置

python
# 默认是开启的
INSTALLED_APPS = [
    # 启动 sessions 应用
    'django.contrib.sessions',
]

MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware'
]

使用

和字典一样

  • 保存session的值到服务器

    python
    request.session['KEY'] = VALUE
  • 获取session的值

    python
    value = request.session['KEY']
    value = request.session['KEY',默认值]
  • 删除session

    python
    del request.session['KEY']

字段配置项

python
SESSION_COOKIE_AGE = 60*60*24*7*2
# 指定sessionid在cookies中的保存时长(默认是两周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# 设置在关闭浏览器时,session就失效

问题

  • django_session表是单表设计;且该表数据量持续增持【浏览器故意删掉sessionid&过期数据未删除】
  • 可以每晚执行python3 manage.py clearsessions【删除已过期的session数据】

返回CSV文件与分页

返回csv文件下载

python
import csv
with open('eggs.csv', 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerrow(['a', 'b', 'c'])

在网站中,要实现下载csv文件,需注意

  • 响应Content-Type类型需修改为text/csv。着告诉浏览器是csv文件,而不是HTML文件
  • 响应会获得额外的Content-Disposition标头,其中包含CSV文件的名称。它将被浏览器用于开启’另存为‘对话框
python
import csv
from django.http import HttpResponse

def some_view(request):
    # Create the HttpResponse object with the appropriate CSV header.
    response = HttpResponse(
        content_type='text/csv',
        headers={'Content-Disposition': 'attachment; filename="somefilename.csv"'},
    )

    writer = csv.writer(response)
    writer.writerow(['First row', 'Foo', 'Bar', 'Baz'])
    writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"])

    return response

分页

python
class Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
'''
Paginator.object_list
必要的一个列表元组QuerySet 或其他具有 count()  __len__() 方法的可切片对象为了实现一致的分页QuerySet 应该是有序的例如使用 order_by() 子句或使用模型上的默认 ordering

对大型 QuerySet 进行分页的性能问题

如果你使用的 QuerySet 有非常多的项目在某些数据库上请求高页数可能会很慢因为产生的 LIMITOFFSET 查询需要计算 OFFSET 记录的数量随着页数的增加需要的时间也就越长

Paginator.per_page
必要的一个页面中包含的最大项目数不包括 orphans参见下面的 orphans 可选参数)。

Paginator.orphans
可选的当你不希望最后一页的项目数量很少时使用这个选项如果最后一页的项目数量通常小于或等于 orphans那么这些项目将被添加到前一页成为最后一页),而不是让这些项目单独留在一页上例如如果有 23 个条目per_page=10,orphans=3,则会有两页第一页有 10 个条目第二页也是最后一页 13 个条目orphans 默认为 0,这意味着页面永远不会合并最后一页可能只有一个项目

Paginator.allow_empty_first_page
可选的是否允许第一页为空如果 False 并且 object_list 是空的则会出现 EmptyPage 错误
'''
Paginator属性
  • Paginator.count:需要分页数据的对象总数
  • Paginator.num_pages:分页后的页面总数
  • page_range:从1开始的range对象,用于记录当前面码数
  • per_page:每页数据的个数
Paginator方法

Paginator对象.page(number)

  • 参数number为页码信息(从1开始)
  • 返回当前number页对应的页信息
  • 如果提供的页码不存在,抛出InvaildPage异常

page对象

  • 创建对象:Paginator对象.page(number)
  • 属性:
    • object_list:当前页上所有数据对象的列表
    • number:当前页的序号,从1开始
    • paginator:当前page对象相关的Paginator对象
  • 方法
    • Page.has_next() 如果有下一页,返回 True。
    • Page.has_previous() 如果有上一页,返回 True。
    • Page.has_other_pages() 如果有下一页 或 上一页,返回 True。
    • Page.next_page_number() 返回下一页的页码。如果下一页不存在,则引发 InvalidPage。
    • Page.previous_page_number() 返回上一页的页码。如果上一页不存在,则引发 InvalidPage。
    • Page.start_index() 返回页面上第一个对象,相对于分页器列表中所有对象的基于 1 的索引。例如,当对一个有 5 个对象的列表进行分页时,每页有 2 个对象,第二页的 start_index() 将返回 3。
    • Page.end_index() 返回页面上最后一个对象相对于分页器列表中所有对象的基于 1 的索引。例如,当对一个有 5 个对象的列表进行分页时,每页有 2 个对象,第二页的 end_index() 将返回 4。

paginator掌控全局,page掌控某一页

例子

python
def test_page(request):
    #/test_page/4
    #/test_page?page=1
    page_num = request.GET.get('page', 1)
    all_data = ['a', 'b', 'c', 'd', 'e']
    # 初始化paginator
    paginator = Paginator.page(int(page_num))
    # 初始化具体页码的page对象
    c_page = paginator.page(int(p))
    return render(request, 'test_page.html', locals())
html
<body>
    {% for p in c_page %}
    <p>
    	{{ p }}
    </p>
    {% endfor %}
</body>
有上一页就为超链,否则为文本
{% if c_page.has_previous %}
	<a href="/test_page?page={{ c_page.previous_page_number }}">上一页</a>
{% else %}
	上一页
{% endif %}
当前页为文本,其他页为超链
{% for p_num in paginator.page_range %}
	{% if p_num == c_page.number %}
		{{ p_num }}
	{% else %}
		<a href="/test_page?page={{ p }}">{{ p_num }}</a>
{% endfor %}
同上上
{% if c_page.has_next %}
	<a href="/test_page?page={{ c_page.next_page_number }}">上一页</a>
{% else %}
	下一页
{% endif %}

内建用户系统

自定义扩展字段的方法

  • 建立一个一对一的表扩展User模型

    以后取字段需要查询

    python
    from django.contrib.auth.models import User
    
    class Employee(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE)
        department = models.CharField(max_length=100)
  • 继承AbstracUser类替换User模型

    仍然可以使用django一套的用户操作

    python
    from django.contrib.auth.models import AbstractUser
    
    class User(AbstractUser):
        pass

    注意:需要在配置文件指定使用自己的用户类

    python
    AUTH_USER_MODEL = 'myapp.MyUser'
  • django建议在项目初期使用替换,项目中期使用一对一

  • 此时引用User就应该使用你自定义的User名或者使用get_user_model()方法

默认用户的字段

  • username
  • password
  • email
  • first_name
  • last_name

创建用户

python
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')

# At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes
# if you want to change other fields.
>>> user.last_name = 'Lennon'
>>> user.save()

更改密码

set_password()

注意不能直接更新数据库中的密码,因为那是非明文操作的

python
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username='john')
>>> u.set_password('new password')
>>> u.save()

验证用户

python
from django.contrib.auth import authenticate
user = authenticate(username='john', password='secret')
if user is not None:
    # A backend authenticated the credentials
else:
    # No backend authenticated the credentials

使用 authenticate() 来验证用户。它使用 username 和 password 作为参数来验证,对每个身份验证后端( authentication backend )进行检查。如果后端验证有效,则返回一个 :class:`~django.contrib.auth.models.User 对象。如果后端引发 PermissionDenied 错误,将返回 None

登录

python
from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

登出

python
from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

限制对未登录用户的访问

python
from django.conf import settings
from django.shortcuts import redirect

# 这里师是重定向到登录界面,并且next保留上一个界面的信息,方便登录后回到之前的界面
def my_view(request):
    if not request.user.is_authenticated:
        return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
    # ...

login_required 装饰器

作用:

  • 如果用户没有登录,会重定向到setting.LOGIN_URL中,并传递绝对路径到查询字符串中
  • 或者直接写url:@login_required(login_url='/accounts/login/')

限制对通过测试的登录用户的访问

你可以方便的调用 user_passes_test 装饰器,当调用返回 False 时会执行重定向。

user_passes_test(test_func, login_url=None, redirect_field_name='next')

redirect_field_name: 与 login_required() 相同。如果你想把没通过检查的用户重定向到没有 "next page" 的非登录页面时,把它设置为 None ,这样它会在 URL 中移除。

python
from django.contrib.auth.decorators import user_passes_test

def email_check(user):
    return user.email.endswith('@example.com')

@user_passes_test(email_check)
def my_view(request):
    ...

类的话继承UserPassesTestMixin # 其他的装饰器都有对应的MIXIN类,可查阅文档

permission_required 装饰器

permission_required(perm, login_url=None, raise_exception=False)

permission_required() 也可以接受可选的 login_url 参数——————去看文档

中间件

中间件的定义

  • 中间是Django请求/响应处理的钩子框架。它是一个轻量级的低级的“插件”系统,用于全局改变Django的输入或输出。
  • 中间件以类的形式体现。
  • 每个中间件组件负责做一些特定的功能。例如,Django包含一个中间组件AuthenticationMiddleware,它使用会话将用户与请求关联起来。

编写中间件

一般是在项目文件夹下面单独建一个middleware文件夹,下面编写middleware.py文件

  • 中间件类必须继承自django.utils.deprecation.MiddlewareMixin类

  • 中间件类须实现下列五个方法中的一个或多个:

    • process_request(self, request)

      执行路由前被调用,在每个请求上调用,返回None或HttpRespose对象

      返回None就可以往后面走

      返回respose就会拦截该请求

    • process_view(self, request, callback, callback_args. callback_kwargs)

      调用视图之前被调用,在每个请求上调用,返回None或HttpResonse对象

    • process_response(self, request, response)

      所有响应返回浏览器被调用,在每个请求上调用,返回HttpResponse对象

    • process_exception(self, request, exception)

      当处理过程中抛出异常时调用,返回一个HttpResponse对象

    • process_template_response(self, request, response)

      在视图函数执行完毕且视图返回的对象中包含render方法时被调用;该方法需要返回实现了render方法的响应对象

    注:中间件中的大多数方法返回None时标识忽略当前操作进入下一项事件,当返回HttpResopnse对象时表示此请求结束,直接返回给客户端

注册中间件

python
# settings.py
MIDDLEWARE = [
    ...
]
# 执行顺序,从上到下,再从下到上

其他(实现限制访问者IP与服务器的请求次数):

request.META['REMOTE_ADDR']可以得到远程客户端的IP地址

request.path_info可以得到客户端访问的请求路由信息

python
class VisitLimit(MiddlewareMixin):
    
    visit_times = {}  # 这里其实应该放到内存里面redis
    
    def process_request(self, request):
        ip_address = request.META['REMOTE_ADDR']
        path_url = request.path_info
        if not re.match('^/test', path_yrl):
            return
        times= self.visit_ime.get(ip_adress, 0)
        print('ip', ip_address, '已经访问', times)
        self.visit_times[ip_address] = time+1
        if times < 5:
            return
        return HttpResponse("您已经访问过" + str(times) + '次, 访问被禁止')

CSRF-跨站伪造请求攻击

某些恶意网站上包含连接、表单按钮或者Javascript,它们会利用登录过的用户再浏览器中的认证信息视图再你的网站上完成某些操作,这就是跨站请求伪造(CSRF, 即Cross-Site Request Forgey)

登录过的网站会将登录状态保存在Cookie里面然后如果没有退出,另一边网站上也会自动提交。

CSRF防范

  • django采用’比对暗号‘机制防范攻击
  • Cookies中存储暗号1,模板中表单里藏着暗号2,用户只有在本网站下提交数据,暗号2才会随表单提交给服务器django比对两个暗号,对比成功,则认为是合法请求,否则是违法请求-403
python
# settings.py中确认中间件中
django.moiddleware.csrf/csrfViewMiddleware是否打开
# form标签下添加如下标签
{% csrf_token %}

局部关闭csrf保护

python
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_view(request):
    return HttpResponse('He')

文件上传

上传规范-前端[HTML]

  • POST
  • <form>中文件上传时必须带有enctype="multipart/from-data"时才会包含文件内容数据。
  • 表单中用<input type="file" name="xxx">标签上传文件
python
def test_upload(request):
    if request.method == "GET":
        return render(request, 'test_upload.html')
    elif request.method == "POST":
        return HttpResponse("--上传文件成功--")
html
<from action="/test_upload" method="post" enctype="multipart/form-data">
	<p>
        <input type="text" name="title">
    </p>
    <p>
        <input type="file" name="myfile">
    </p>
    <p>
        <input type="submit" value="上传">
    </p>
</from>

上传规范-后端[Django]

视图函数中,用request.FILES取文件框的内容

python
file=request.FILE['XXX']
'''
说明:
1. FILES的key对应页面中file框的name值
2. file绑定文件流对象
3. file.name文件名
4. file.file文件的字节流数据
'''
python
'''
配置文件的访问路径和存储路径
在setting.py中设置MEDIA相关配置;Django把用户上传的文件,统称为media资源
Django把用户上传的文件,统称为media资源
'''
# file : settings.py
MEDIA_URL = '/media/'
MWDIA_ROOT = os.path.join(BASE_DIR, 'media')

其中,MEDIA_URL和MEDIA_ROOT需要手动绑定

python
# 步骤:主路由中添加路由
from django.conf import settings
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
'''
等价于做了MEDIA_URL开头的路由,Django接到该特征请求后取MEDIA_ROOT路径查找资源
'''

文件写入

python
# 方案1:传统的open方式
def upload_view(request):
    if request.method == 'GET':
        return render(request, 'test_upload.html')
    elif request.method == 'POST':
        a_file = request.FILES['myfile']
        print("上传文件名是:", a_file.name)
        filename = os.path.join(setting.MEDIA_ROOT, a_file.name)
        with open(filename, 'wb') as f:
            data = a_file.file.read()
            f.write(data)
            
        return HttpResponse("接收文件:" + a_file.name + "成功")
# 有一些问题,比如文件重名等细节
python
# 方案2:借助ORM
# 字段:FileField(upload='子目录名')

@csrf_exempt
def upload_view_dj(request):
    if request.method == 'GET':
        return render(request, 'test_upload.html')
    elif request.method == 'POST':
        title = request.POST['title']
        a_file = request.FILES['myfile']
        # 就这下面一项就可以存
        Content.objects.create(desc=title, myfile=a_file)
        return HttpResponse('----upload is ok----')

邮件

邮件相关协议-SMTP

  • SMTP的全称是"Simple Mail TransferProtocol",即简单邮件传输协议(25号端口)
  • 它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转
  • 属于”推送“协议

邮件相关协议IMAP

  • IMAP的全称是Internet Mail Access Protocol,即交互式邮件访问协议,是一个应用层协议(端口143)
  • 用来从本地邮件客户端(outlook, foxmail等)访问远程服务器上的邮件
  • 属于”拉取“协议

邮件相关协议POP3

  • TCP/IP中的一员(端口110)
  • 本协议主要用于支持使用客户端远程管理在服务器上的电子邮件
  • 属于"拉取"协议

对比

  • IMAP具备摘要浏览功能,可预览部分摘要,再下载整个邮件

  • IMAP为双向协议,客户端操作可反馈给服务器

  • POP3必须下载全部邮件,无摘要功能

  • POP3为单向协议,客户端操作无法同步服务器

Django发邮件

  • Django中配置邮件功能,主要为SMTP协议,负责发邮件
  • 原理:
    • 给djangp授权一个邮箱
    • django用给邮箱给对应发件人发送邮件
    • django.core.mail封装了电子邮件的自动发送SMTP协议
配置
python
# 先获取SMTP服务的授权码
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'SMTP.qq.com'
EMAILPORT = 25 # SMTP服务的端口号
EMAIL_HOST_USER = 'xxxx@qq.com' # 发送邮件的qq邮箱
EMAIL_HOST_PASSWORD = '******' # QQ邮箱->设置->账户->'POP3/IMAP...服务'里面得到的授权码
EMAIL_USE_TLS = False # 是否启动TLS链接(安全链接)默认False--比较费时
调用
python
from django.core import mail
mail.send_mail(
	subject,  # 题目
    message,  # 消息内容
    from_email,  # 发送者[当前配置邮箱]
    recipient_list=['xxx@qq.com'], # 接收者邮件列表
)
其他--捕获异常

向开发人员邮箱发送(tranceback.format_exc())而不是(exception)

缓存

缓存

是一类可以更快的读取数据的介质统称,一般用来存储临时数据。

视图渲染有一定的成本,数据库频繁查询过高,所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数,用户拿到响应的时间成本会更低。

python
given a URL, try finding that page in the cache
if the page is in the cache:
    return the cache page
else:
    generate the page
    save the generated page in the cache (for next time)
    return the generated age

Django中设置缓存-数据库缓存

将缓存的数据存储在您的数据库中

尽管存储介质没有变化,但是当把一次负责查询的结果直接存储到表里面,比如多个条件的过滤查询结果,课避免进行复杂的查询,提升效率;

python
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
        'TIMEOUT': 300, # 缓存保存时间 单位秒,默认值为300
        'OPTIONS': {
            'MAX_ENTRIES': 300, # 缓存最大数据条数
            'CULL_FREQUENCY': 2, # 缓存条数达到最大值时,删除1/x的缓存数据
        }
    }
}

Django中设置缓存-本地内存缓存

数据缓存到服务器内存中

python
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

还有文件系统缓存等

https://docs.djangoproject.com/zh-hans/3.2/topics/cache/

Django中使用缓存-视图函数中

python
from django.views.decorators.cache import cache_page

@cache_apge(30)  -> 单位s
def my_view(request):
    pass

Django中使用缓存-路由中

python
from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/', cache_page(60)(my_view)),
]

缓存API的使用(局部缓存)

上面的两个例子都是整体缓存,但灵活性低并且复用性差,是直接缓存的整个界面,而如果直接缓存sql结果的话就能解决这些问题

先引入cache对象
  • 使用caches['CACHE配置key']导入具体对象

    这个就是配置中配置了多个配置项使用

    python
    from django.care.cache import caches
    
    cache1 = caches['myalias']
    cacahe2 = caches['myalias_2']
  • from django.core.cache import cache 相当于直接引入CACEHS配置项中的'default'项

使用
python
cache.set(key, value, timeout) # 存储缓存
'''
key: 缓存的对象
value: python对象
timeout: 缓存存储时间(s),默认为CACHES中的TIMEOUT值
返回值None
'''
python
cache.get(key) # 获取缓存(温柔地取)
cache.add(key, value) # 存储缓存,仅在key不存在时生效

其他方法:

python
cache.get_or_set(key, value, timeout) # 如果未获取到数据,则执行set操作
cache.set_many(dict, timeout) # 批量存储缓存
cache.get_many(key_list)
cache.delete(key)
cache.delete_many(key_list)2

浏览器缓存策略

强缓存

不会向服务器发送请求,直接从缓存中读取资源

  • 响应头-Expires(绝对时间)

    定义:缓存过期时间,用来指定资源到期的时间,是服务端的集体的时间点

    样例:Expires:Thu,02 Apr 2030 05......

  • 响应头-Cache-Control(相对时间)

    在HTTP/1.1中,Cache-Control主要用于控制网页缓存。比如Cache-Control:max-age=120表示请求创建时间的120秒后缓存失效

说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用Cache-Control

协商缓存

强缓存中的数据一旦过期,还需要更服务器进行通信,从而获取最新数据

如果强缓存中的数据是一些静态文件、大图片等呢?

解答:考虑到大土拍你这类比较消耗贷款且不易变化的数据,强缓存时间到期后,浏览器会去跟服务器协商,当前缓存是否可用,如果可用,服务器不必返回数据,浏览器继续使用原来缓存的数据,如果文件不可用,则返回最新数据。

Last-Modified响应头和If-Modified-Since请求头

协商缓存是在强缓存基础之下的,所以正常情况下,协商缓存回避强缓存多上面的这个头

Last-Modified:文件的最近修改时间

If-Modified-Since:当缓存到期后,浏览器将获取到的Last-Modified值作为请求头If-Modified-Since的值,与服务器发请求协商,服务端返回304响应码【响应体为空】,代表缓存继续使用,200响应码代表缓存不可用【响应体为最新资源】

Etag响应头和If-None-Match请求头

(现在一般用这个,这个算的是哈希来判断是否更改,上面那个是时间到秒:修改时间来判断)

  • Etag时服务器响应请求时,返回当前资源文件的一个唯一标识(有服务器生成),只要资源有变化,Etag就会重新生成;
  • 缓存到期后,浏览器将Etag响应头的值做为If-None-Match请求头的值,给服务器发起请求协商;服务器街道请求头后,比对文件标识,不一致则认为资源不可用,返回200响应码【响应体为最新资源】;可用则返回304响应码