文章目录:
  1. 安装
    1. 常用命令
  2. 简介
    1. 项目结构
  3. 模板
    1. 变量
    2. 列表
    3. 字典
    4. 过滤器
      1. default
      2. length
      3. filesizeformat
      4. date
      5. truncatechars
    5. 标签
      1. if/else 标签
      2. for标签
    6. 配置静态文件
    7. 模板继承
      1. 父模板
      2. 子模板
  4. 模型
    1. 模型配置
    2. 数据库操作
      1. 增
      2. 删
      3. 改
      4. 查
      5. 所有常用的函数
  5. 表单
    1. HTTP请求
    2. 基本用法
    3. ModelForm
      1. 单独字段验证
      2. 表单整体验证
  6. 路由
    1. 路由分发
    2. 反向解析
    3. 路径转换器
  7. Admin管理工具
  8. 视图
    1. 请求对象:HttpRequest
    2. 响应对象:HttpResponse
    3. 其他函数

参考:

官方文档 Django 文档 | Django documentation | Django

菜鸟教程 Django 教程 | 菜鸟教程

哔哩哔哩

安装

直接pip install django

常用命令

创建项目:使用Pycharm Pro 或 django-admin startproject ProjectName

启动服务:python manage.py runserver 8000

创建应用:python manage.py startapp appName

创建管理员:python manage.py createsuperuser

生成迁移文件:python manage.py makemigrations

应用迁移:python manage.py migrate

简介

Django是一种Web开发框架,遵从MTV(Model-Template-View)原则。

Model负责与数据库交互,在model.py中可以配置数据的结构、类型、相关函数。

View为视图,用户访问URL时会被urls.py映射到对应的View中,View内部调用Model进行数据的处理、数据库操作等,可以将数据传递到模板当中。

Template为模板,一般使用HTML+CSS+JS+Django中特定的规则完成,接受View传递的数据,渲染为HTML,显示在前端。

项目结构

创建项目后会出现以下的目录:
来自菜鸟教程

一般主项目需要修改的只有settings.py的简单配置,和urls.py里面的路由配置。

创建应用后,应用下的结构:

myapp/
│
├── migrations/
│   └── __init__.py
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── tests.py
├── urls.py
└── views.py

// From 菜鸟教程

models.py定义数据模型,views.py处理业务逻辑,admin.py配置管理后台,有时候会把路由映射放到应用中处理,再创建一个urls.py。

模板

Django的模板有变量渲染、逻辑控制、模板继承、静态文件、自定义过滤器等功能。

需要先向Django说明模板文件的路径,将TEMPLATES中的 DIRS 为 [BASE_DIR / "templates"]。views.py中使用render函数作为响应,如:

from django.shortcuts import render
 
def runoob(request):
    context          = {}
    context['hello'] = 'Hello World!'
    return render(request, 'runoob.html', context)

使用字典context作为参数,键值hello对应模板中变量{{ hello }}

变量

view:{"HTML变量名" : "值"}# 使用字典传输
HTML:{{变量名}}

如上文可以使用<p>{{ hello }}</p>可以打印Hello World!。

列表

使用.索引下标可以取出对应列表中的元素。

如views.py:

from django.shortcuts import render

def runoob(request):
    views_list = ["菜鸟教程1","菜鸟教程2","菜鸟教程3"]
    return render(request, "runoob.html", {"views_list": views_list})

templates/runoob.html:

<p>{{ views_list }}</p>   # 取出整个列表
<p>{{ views_list.0 }}</p> # 取出列表的第一个元素

字典

可以用 .键 取出对应的值。如:

from django.shortcuts import render

def runoob(request):
    views_dict = {"name":"菜鸟教程"}
    return render(request, "runoob.html", {"views_dict": views_dict})
<p>{{ views_dict }}</p>
<p>{{ views_dict.name }}</p>

过滤器

相当于一种非常便捷的库函数,可以用于处理字符串等数据。模板语法:

{{ 变量名 | 过滤器:可选参数 }}

过滤器可以被套接,一个过滤器管道的输出可以作为下一个管道的输入。如:

{{ my_list|first|upper }}

以下是一些常用的过滤器:

default

default 为变量提供一个默认值。

如果 views 传的变量的布尔值是false,则使用指定的默认值。以下值都是false:

0  0.0  False  0j  ""  []  ()  set()  {}  None

实际应用如:

from django.shortcuts import render

def runoob(request):
    name =0
    return render(request, "runoob.html", {"name": name})
{{ name|default:"菜鸟教程666" }}
length

返回对象的长度,适用于字符串和列表。

字典返回的是键值对的数量,集合返回的是去重后的长度。

from django.shortcuts import render

def runoob(request):
    name ="菜鸟教程"
    return render(request, "runoob.html", {"name": name})
{{ name|length}}
filesizeformat

以更易读的方式显示文件的大小(即'13 KB', '4.1 MB', '102 bytes'等)。

字典返回的是键值对的数量,集合返回的是去重后的长度。

{{ num|filesizeformat}}
date

根据给定格式对一个日期变量进行格式化。

格式Y-m-d H:i:s返回 年-月-日 小时:分钟:秒 的格式时间。

{{ time|date:"Y-m-d" }}
truncatechars

如果字符串包含的字符总个数多于指定的字符数量,那么会被截断掉后面的部分。截断的字符串将以 ... 结尾。

{{ views_str|truncatechars:2}}

标签

标签使用 {% %}的格式。

上面都是对数据进行处理,下面是一些逻辑的语句:

if/else 标签

基本语法格式:

{% if condition1 %}
   ... display 1
{% elif condition2 %}
   ... display 2
{% else %}
   ... display 3
{% endif %}

根据条件判断是否输出。if/else 支持嵌套。

{% if %}标签接受 and , or 或者 not 关键字来对多个变量做判断 ,或者对变量取反(not)。

for标签

与 Python 的 for 语句的情形类似,循环语法是 for X in Y ,Y 是要迭代的序列而 X 是在每一个特定的循环中使用的变量名称。

每一次循环中,模板系统会渲染在{% for %}和 {% endfor %}之间的所有内容。

如遍历athlete_list:

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>

在标签中可以通过{{forloop.xxxx}}获取循环序号:

{% empty %}:再循环为空时执行(即 in 后面的参数布尔值为 False )。如下所示:

from django.shortcuts import render

def runoob(request):
  views_list = []
  return render(request, "runoob.html", {"listvar": views_list})
{% for i in listvar %}
    {{ forloop.counter0 }}
{% empty %}
    空空如也~
{% endfor %}

配置静态文件

1、在项目根目录下创建 statics 目录。

来自菜鸟教程

2、在 settings 文件的最下方配置添加以下配置:

STATIC_URL = '/static/' # 别名 
STATICFILES_DIRS = [ 
    os.path.join(BASE_DIR, "statics"), 
]

3、在 statics 目录下创建 css 目录,js 目录,images 目录,plugins 目录, 分别放 css文件,js文件,图片,插件。

4、把 bootstrap 框架放入插件目录 plugins。

5、在 HTML 文件的 head 标签中引入 bootstrap。

<link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7/dist/css/bootstrap.css">

此时需要使用别名,而不是真实路径statics,这样做方便修改外部访问路径而不需要修改真实位置,且对于真实路径也有保护。

在模板中使用需要加入 {% load static %} 代码,模板标签{% static '...' %},其中的点为相对于static目录的相对路径,以下实例我们从静态目录中引入图片。

from django.shortcuts import render

def runoob(request):
    name ="菜鸟教程"
    return render(request, "runoob.html", {"name": name})
{% load static %}
{{name}}<img src="{% static 'images/runoob-logo.png' %}" alt="runoob-logo">

模板继承

模板可以用继承的方式来实现复用,减少冗余内容。

网页的头部和尾部内容一般都是一致的,我们就可以通过模板继承来实现复用。

父模板用于放置可重复利用的内容,子模板继承父模板的内容,并放置自己的内容。

父模板
{% block 名称 %} 
预留给子模板的区域,可以设置设置默认内容
{% endblock 名称 %}
子模板
{% extends "父模板路径"%} ]

{ % block 名称 % }
内容 
{% endblock 名称 %}

如下面的示例:

base.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
    <h1>Hello World!</h1>
    <p>菜鸟教程 Django 测试。</p>
    {% block mainbody %}
       <p>original</p>
    {% endblock %}
</body>
</html>

index.html

{%extends "base.html" %}
 
{% block mainbody %}
<p>继承了 base.html 文件</p>
{% endblock %}

模型

直接使用SQLite数据库。

模型配置

模型在Model/models.py中配置。

首先需要引入库 from django.db import models

定义表:

class Book(models.Model):
    name = models.CharField(max_length=20)

models.Model是统一继承的类,后面的CharField是变量类型,后面括号的max_length=为可选择的参数。

常用的变量类型和参数:

字段类型描述示例
AutoField自增整数主键,通常用于ID字段id = models.AutoField(primary_key=True)
BigAutoField大整数自增主键,适用于需要很大id的表id = models.BigAutoField(primary_key=True)
IntegerField普通整数age = models.IntegerField()
BigIntegerField大整数big_num = models.BigIntegerField()
PositiveIntegerField非负整数score = models.PositiveIntegerField()
PositiveSmallIntegerField非负小整数small_num = models.PositiveSmallIntegerField()
SmallIntegerField小整数small_num = models.SmallIntegerField()
FloatField浮点数price = models.FloatField()
DecimalField定点数(需要指定max_digits和decimal_places)cost = models.DecimalField(max_digits=5, decimal_places=2)
CharField字符串,需要指定max_lengthname = models.CharField(max_length=100)
TextField长文本,不限制长度description = models.TextField()
BooleanField布尔值is_active = models.BooleanField(default=True)
NullBooleanField(废弃)可空布尔值 Django3.1后移除改用 BooleanField(null=True)
DateField日期birthday = models.DateField()
DateTimeField日期时间created_at = models.DateTimeField(auto_now_add=True)
TimeField时间start_time = models.TimeField()
DurationField时间间隔duration = models.DurationField()
EmailField邮箱格式字符串email = models.EmailField()
URLFieldURL格式字符串website = models.URLField()
FileField文件上传字段upload = models.FileField(upload_to='uploads/')
ImageField图片上传字段,需要Pillow库photo = models.ImageField(upload_to='photos/')
ForeignKey外键,关联另一模型author = models.ForeignKey(User, on_delete=models.CASCADE)
OneToOneField一对一字段profile = models.OneToOneField(Profile, on_delete=models.CASCADE)
ManyToManyField多对多字段tags = models.ManyToManyField(Tag)
参数名描述示例
null是否允许在数据库存储NULL(不等同于空字符串)null=True
blank是否允许该字段在Django表单中留空(字段验证时是否必填)blank=True
default字段默认值default=0
primary_key是否为主键字段primary_key=True
unique是否唯一(数据库层面唯一索引)unique=True
choices限制字段的可选值,传入tuple列表status = models.CharField(max_length=2, choices=STATUS_CHOICES)
max_length最大长度,仅对CharField和TextField(部分字段)适用max_length=255
db_index是否建立数据库索引db_index=True
editable是否在admin或modelform中编辑editable=False
verbose_name字段的人类可读名称verbose_name='用户名'
help_text字段帮助信息help_text='请输入用户名'
auto_now设为True时自动设置当前时间戳(每次更新记录时)auto_now=True
auto_now_add设为True时自动设置创建时间戳(只在记录创建时设置)auto_now_add=True
upload_to文件/图片上传字段,指定文件存储目录upload_to='photos/%Y/%m/%d'
on_delete外键关联对象被删除时的行为on_delete=models.CASCADE

常用的外键on_delete选项:

选项含义
models.CASCADE级联删除,关联对象删除了,当前对象也删除
models.PROTECT禁止删除关联对象,抛出异常
models.SET_NULL关联对象删除时,把外键字段置为 NULL
models.SET_DEFAULT关联对象删除时,把外键字段设为默认值
models.SET(...)关联对象删除时,把外键字段设为指定的值
models.DO_NOTHING什么都不做,数据库会报错(不推荐)

数据库操作

首先都需要从模型文件中引入类,如:from TestModel.models import Test

增
def adddb(request):
    test1 = Test(name='nantong')
    test1.save()
删
def testdb(request):
    # 删除id=1的数据
    test1 = Test.objects.get(id=1)
    test1.delete()
    
    # 另外一种方式
    Test.objects.filter(id=1).delete()
    
    # 删除所有数据
    Test.objects.all().delete()
改
def testdb(request):
    # 修改其中一个id=1的name字段,再save
    test1 = Test.objects.get(id=1)
    test1.name = 'Google'
    test1.save()
    
    # or
    Test.objects.filter(id=1).update(name='Google')
    
    # 修改所有的列
    Test.objects.all().update(name='Google')
查
def testdb(request):
    # 初始化
    response = ""
    response1 = ""
    
    # 通过objects这个模型管理器的all()获得所有数据行,相当于SQL中的SELECT * FROM
    listTest = Test.objects.all()
        
    # filter相当于SQL中的WHERE,可设置条件过滤结果
    response2 = Test.objects.filter(id=1) 
    
    # 获取单个对象
    response3 = Test.objects.get(id=1) 
    
    # 限制返回的数据 相当于 SQL 中的 OFFSET 0 LIMIT 2;
    Test.objects.order_by('name')[0:2]
    
    #数据排序
    Test.objects.order_by("id")
    
    # 上面的方法可以连锁使用
    Test.objects.filter(name="runoob").order_by("id")
所有常用的函数
  1. .all()

返回所有记录的 QuerySet(类似列表,但是惰性查询,可链式调用)

resources = Resource.objects.all()

  1. .filter(**kwargs)

过滤数据,返回满足条件的 QuerySet。kwargs 是字段名+条件,支持双下划线查询。

# 查找状态是 normal 的
qs = Resource.objects.filter(status="normal")

# 查找标题包含 "python" 的(不区分大小写)
qs = Resource.objects.filter(title__icontains="python")

# 过滤上传者为当前用户,且状态不是 removed
qs = Resource.objects.filter(uploader=request.user).exclude(status="removed")

# 多条件“且”关系,会默认 AND
qs = Resource.objects.filter(status="normal", file_type="pdf")

  1. .exclude(**kwargs)

排除满足条件的记录,返回其余记录。

qs = Resource.objects.exclude(status="removed")

  1. .get(**kwargs)

获取唯一一条符合条件的记录。如果没有或者多于一条,会抛出异常。

res = Resource.objects.get(pk=1)

一般搭配 try...except 使用。


  1. .order_by(*fields)

排序。字段名前加 - 表示倒序。

res = Resource.objects.all().order_by("-created_at")  # 按创建时间倒序

  1. .values(*fields) 和 .values_list(*fields, flat=False)

只查询部分字段,用于优化性能。

names = Resource.objects.values_list("title", flat=True)  # 只要标题列表

data = Resource.objects.values("id", "title")   # 返回字典列表 [{id:1,title:"..."}, ...]

  1. .first() 和 .last()

拿 QuerySet 的第一条/最后一条数据,方便快捷。可能返回 None。

res = Resource.objects.filter(status="normal").first()

  1. .count()

统计符合条件的数据数量。

count = Resource.objects.filter(status="normal").count()

  1. .exists()

判断符合条件的记录是否存在,效率高。

has_pdf = Resource.objects.filter(file_type="pdf").exists()

  1. .update(**kwargs)

一次批量更新符合条件的记录,不会触发 save() 方法。

Resource.objects.filter(status="pending").update(status="removed")

  1. .create(**kwargs)

创建并保存一条新记录,快捷写法。

Resource.objects.create(title="新资源", uploader=request.user, status="normal")

  1. .update_or_create(defaults=None, **kwargs)

尝试查找满足条件的记录,有则更新,没有则创建。

obj, created = Resource.objects.update_or_create(
    file_hash="xxx123",
    defaults={"title": "新标题", "status": "normal"},
)

  1. .get_or_create(defaults=None, **kwargs)

查找是否存在满足条件的对象,没有则创建,返回对象和是否创建标志。

obj, created = Resource.objects.get_or_create(title="Unique title",
                                              defaults={"status": "normal"})

  1. .aggregate() 和 .annotate()

用于聚合查询,比如计数、求和、最大值等。需要导入 django.db.models 中的聚合函数。

from django.db.models import Count, Sum

# 统计所有资源的总下载次数
total_downloads = Resource.objects.aggregate(total=Sum("downloads"))["total"]

# 给每个上传者统计上传资源数
users = User.objects.annotate(resource_count=Count("resource"))

  1. 复杂查询 - 使用 Q() 对象

实现 “或” 逻辑或复杂逻辑。

from django.db.models import Q

qs = Resource.objects.filter(Q(title__icontains="python") | Q(summary__icontains="python"))

表单

Django的表单功能通过django.forms提供,可以渲染HTML表单,自动生成网页上的表单数据;验证用户的输入是否符合要求;获取清洗后的数据,将用户输入转化为Python数据类型;关联数据类型,把表单和数据库模型相关联。

HTTP请求

GET:请求服务器数据,请求数据附加在URL后面

POST:提交数据在请求体中

基本用法

创建一个表单类:一般在forms.py中,比如下面这个联系人表单类

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(label='你的名字', max_length=100)
    email = forms.EmailField(label='邮箱')
    message  = forms.CharField(label='留言', widget=forms.Textarea)

视图中显示表单:views.py

from django.shortcuts import render
from .forms import ContactForm

def contact_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)  # 带数据实例化表单,准备验证
        if form.is_valid():  # 验证表单数据
            # 获取表单清洗后的数据
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            message = form.cleaned_data['message']
            # TODO: 处理数据,如保存或发送邮件
            return render(request, 'thanks.html', {'name': name})
    else:
        form = ContactForm()  # 空表单实例
    
    return render(request, 'contact.html', {'form': form})

模板中渲染表单:contact.html

<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">提交</button>
</form>

ModelForm

如果已经定义好了模型,那么可以使用ModelForm简化模型相关表单。

假设你有一个models.py:

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()

创建对应的表单:

# forms.py
from django.forms import ModelForm
from .models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content']  # 指定需要包含的字段

然后在视图中使用:

def create_article(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            form.save()  # 直接保存到数据库
            return redirect('article_list')
    else:
        form = ArticleForm()
    return render(request, 'article_form.html', {'form': form})

表单验证中可以使用其他高级方法。

单独字段验证

比如对于某个单独字段进行额外验证,可以在ModelForm中定义一个cleam_<fieldname>方法,如下:

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = '__all__'

    def clean_price(self):
        price = self.cleaned_data.get('price')
        if price <= 0:
            raise forms.ValidationError('价格必须大于0')
        return price

clean_price方法会自动被调用,验证price字段。

表单整体验证

如果需要基于多个字段一起验证,可以重写clean方法:

def clean(self):
    cleaned_data = super().clean() # 因为重写了,所以先调用父类原有的clean()方法
    title = cleaned_data.get('title')
    author = cleaned_data.get('author')
    if title == author:
        raise forms.ValidationError('书名和作者不能相同')

路由

路由是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,也就是 URL 与 Django 的视图建立映射关系。路由在urls.py中配置。

from django.urls import path  
urlpatterns = [ 
    path('admin/', admin.site.urls), 
    path('index/', views.index),
]

路由分发

路由分发是把路由的分配分发到每个app目录当中,使用的是include,比如:

from django.contrib import admin 
from django.urls import path,include # 需引入include 
urlpatterns = [ 
    path('admin/', admin.site.urls), 
    path("app01/", include("app01.urls")), 
    path("app02/", include("app02.urls")), 
]

反向解析

随着功能的增加,路由层的 url 发生变化,就需要去更改对应的视图层和模板层的 url,非常麻烦,不便维护。

可以利用反向解析,当路由层 url 发生改变,在视图层和模板层动态反向解析出更改后的 url,免去修改的操作。

反向解析一般用在模板中的超链接及视图中的重定向。

这时候在路由中添加字段 name='BieMing',

path('login1/', views.login, name='login')

在views.py中可以引入reverse进行反向解析:

return redirect(reverse('login'))

路径转换器

用来捕获URL路径中的某部分,并将其转换成特定类型变量,可以从 URL 中提取参数并传递给视图。比如:

path('article/<int:id>/', views.article_detail)

这里 <int:id> 是一个路径转换器,表示从 URL 中匹配一个整数,并把它命名为 id,传给视图函数。

而在views.py中,可以用第二个变量接受这个值:

def index(request, id:int):
    ...
转换器类型匹配说明传给视图的变量类型
str匹配非斜杠(/)的任意字符序列字符串(str)
int匹配正整数(由数字组成)整数(int)
slug匹配字母、数字、连字符和下划线字符串(str)
uuid匹配UUID格式的字符串UUID 对象
path匹配任意字符(包含斜杠 /)字符串(str)

Admin管理工具

需要先注册数据模型到 admin。比如,先在 TestModel 中创建了模型 Test 。

修改 TestModel/admin.py:

from django.contrib import admin
from TestModel.models import Test
 
# Register your models here.
admin.site.register(Test)

也可以一次导入多个模型:

admin.site.register([Test, Contact, Tag])

还有其他的管理方法,不过小学期任务展示下前端就行了,管理后台没必要做的很完善()

视图

请求对象:HttpRequest

1. GET

数据类型是 QueryDict,一个类似于字典的对象,包含HTTP GET的所有参数。这些参数就是URL最后的/?name=北航&year=2024

如果有相同的键,就把所有的值放到对应的列表里,取值格式:对象.方法,request即请求的对象。

可以使用和模型一样的方法,都在GET下,如get()返回最后一个匹配的字符串。

def index(request):
    name = request.GET.get("name")
    return HttpResponse('姓名: {}'.format(name))

如果存在第二个参数,则表示不存在时的默认值(避免报错)。

2. POST

也是一种HTTP请求方式,请求参数放在请求体(body)中,不显示在URL中。数据类型同样是 QueryDict。

使用方法和GET几乎一模一样。

def index(request):
    name = request.POST.get("name")
    return HttpResponse('姓名: {}'.format(name))

3. path

用来获取URL中的路径部分,数据类型是字符串。

def index(request):
    name = request.path
    print(name)
    ...

响应对象:HttpResponse

响应对象主要有三种形式:HttpResponse()、render()、redirect()。

1. HttpResponse()

返回文本,参数为字符串,字符串中写文本内容。如果参数为字符串里含有 html 标签,也可以渲染。

def index(request):
    return HttpResponse("<a href='https://lixu.cc'>点击访问</a>")

2. render()

返回文本,第一个参数是 request,第二个参数为字符串(页面名称),第三个参数为字典(可选参数,向页面传递的参数:键为页面参数名,值为views参数名)。

def index(request):
    name = "LX的博客"
    return render(request, "index.html", {"name": name})

3. redirect()

用于重定向,跳转新页面。参数为字符串,字符串中填写页面路径。一般用于 form 表单提交后,跳转到新页面。可以传入 URL 名称(反向解析)或路径。

举例:redirect("detail", pk=1) 会跳转到名为 detail 的路由,参数 pk=1。

其他函数

1. get_object_or_404(Model, **kwargs)

从数据库中去获取指定条件的对象(比如 Resource 表中的一条记录)。如果找不到,则直接返回 404 页面,避免程序继续执行和报错。

例:get_object_or_404(Resource, pk=pk):查找主键为 pk 的资源。

2. messages

Django 的内置消息框架,用于控制器(视图)向模板传递状态信息(如“操作成功”、“错误提示”等)。

messages.success(request, "成功消息")、messages.error(request, "错误消息") 等,都可以给前端发送提示。


3. @login_required

装饰器,保证访问此视图的用户已登录。未登录则跳转至登录页。

使用它能方便地保护一些只能登录用户使用的操作,如上传、评分、举报等。


4. Q 对象

Django ORM 的高级查询工具,可以用来组合复杂的查询条件,实现“或”、“且”等关系。

示例:Q(title__icontains=q) | Q(summary__icontains=q) 意思是:标题或摘要包含关键词 q。