目录

Making queries 进行查询

首先是创建表模型类

from django.db import models

class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField() def __str__(self): # __unicode__ on Python 2
return self.name class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField() def __str__(self): # __unicode__ on Python 2
return self.name class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField() def __str__(self): # __unicode__ on Python 2
return self.headline

创建一个对象(一条数据记录)

在 django 中要想创建一个数据对象,只需要实例化他,传入这个表模型类的关键字参数,然后调用 .save() 方法把这个对象保存到数据库中即可

from blog.models import Blog

b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
b.save()

保存修改的表对象

想要保存一个已经改动过的对象,调用 .save() 方法即可(更新一条记录)

b5.name = 'New name'
b5.save() # 代码运行到这里才会进行数据库操作,真正地更新到数据库!

保存外键字段或多对多字段(ForeignKey or ManyToManyField fields)

外键字段 ForeignKey

from blog.models import Blog, Entry

entry = Entry.objects.get(pk=1)  # 查出主键是 1 的 entry 对象(记录)
cheese_blog = Blog.objects.get(name="Cheddar Talk")
entry.blog = cheese_blog # 将 blog 对象直接赋值给 entry 对象的 blog 属性(对象赋值给字段)
entry.save() # 调用 .save() 方法

ManyToManyField

想要保存多对多字段,写法和外键字段有点小区别,使用 .add() 方法来记录一个关系。

下面的案例把 Author 表模型类的实例 joe 对象增加到了 entry 对象中(加了一个对应关系)

from blog.models import Author

joe = Author.objects.create(name="joe")  # 创建了一个 Author类的对象 joe, 它的 name 属性是 joe
entry.authors.add(joe) # 给已经查出来了的 entry 对象增加...(建立关联关系)

插入多个多对多字段

john = Author.objects.create(name="John")
paul = Author.objects.create(name="Paul")
george = Author.objects.create(name="George")
ringo = Author.objects.create(name="Ringo")
entry.authors.add(john, paul, george, ringo)

注意!把 add 方法解释一下(对象可以直接传,如果是值,要传元组?)

...

Retrieving objects 查出对象(QuerySet)

从数据库中查询对象,通过 表模型类的 Manager 管理器 来构造一个 QuerySet 。

一个 QuerySet 代表着你数据库中的一系列对象的集合,它可以是 0 个、 1 个 或者多个 filters,filters 可以基于你给出的参数 缩小查询结果的范围,对于 SQL ,一个 QuerySet 相当于一个 SELECT 语句,一个 filter 就相当于一个限制条件,比如 WHERE 或者 LIMIT。

我们通过使用表模型类的 Manager 来构造(获得)一个 QuerySet ,每一个表模型类至少有一个 Manager ,他可以直接被对象调用(封装好了的),我们可以通过表模型类直接访问它,就像下面这样:

Blog.objects
# 内部对应的是:<django.db.models.manager.Manager object at ...>
b = Blog(name='Foo', tagline='Bar') # 表模型类实例化会返回实例化好的对象
b.objects # 会报错 AttributeError: "Manager isn't accessible via Blog instances." --> Manager 不能通过 Blog 实例来访问

Retrieving all objects 查出所有对象

最简单的方式从表里获取全部记录对象(QuerySet)是在 Manager 上调用 .all() 方法

all_entries = Entry.objects.all()  # 查出 Entry 模型类对应表中的所有数据记录,是一个 QuerySet

Retrieving specific objects with filters 通过 filter 查出指定的对象

.all() 方法可以返回 数据库中所有记录的对象 但是通常情况下,我们只需要查询出里面的一小部分对象。

想要得到这么一小部分对象,我们需要细化(约束)最初的 QuerySet ,增加过滤条件,细化 QuerySet 最常用的两种写法如下:

filter(**kwargs)

返回一个符合你给出的查找参数(条件)的 QuerySet(满足条件的)

exclude(**kwargs)

返回一个不符合你给出的查找参数(条件)的 QuerySet(不满足条件的)

查找参数(**kwargs)应该符合 Field lookups(字段查找)格式(day 55 博客里有)

就比如,你想得到 2006年发表的 blog entries 的 QuerySet ,可以通过 filter 这么实现:

Entry.objects.filter(pub_date__year=2006)
# 也可以这么写(使用 表模型类的 Manager 来调用 filter)
Entry.objects.all().filter(pub_date__year=2006)

链式 filter

细化 QuerySet 后的结果自身还是一个 QuerySet ,所以我们可以链式地细分(再次筛选)它,比如:

Entry.objects.filter(
headline__startswith='What'
).exclude(
pub_date__gte=datetime.date.today()
).filter(
pub_date__gte=datetime.date(2005, 1, 30)
) # 不写成一行是这样看起来更清楚点

上面语句的最终查询条件(含义)是:查出所有 headline 以 “What” 开头,pub_date 非今天及今天以后,并且 pub_date 是在 2005-1-30 之后的 QuerySet (包含了满足条件的记录)

大白话一点就是:查出 标题以 What 开头,在2005年1月30日至今(天)的所有书籍记录(QuerySet)

QuerySet 是相互隔离的(不同的对象了)

每一次你细化 QuerySet,你将得到一个崭新的 QuerySet,他跟细分之前的 QuerySet 没有绑定关系,互不影响。

每次细化都会创建一个单独而又独特的 QuerySet 对象,它可以被用来存储、使用、重用。

q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())

上面的三个 QuerySet 是相互独立的

第一个 QuerySet 包含了所有的 文章标题(headline) 以 What 开头的 QuerySet 对象(记录对象集合)

第二个 QuerySet 是第一个集合的子集合(再次筛选后的对象),附加条件:pub_date 不是(exclude)今天或者将来的 --> 今天及今天之前的

第三个 QuerySet 是第一个集合的子集合(在第一个的条件上再加条件),附加条件:pub_date 是今天或者将来的

第一个 QuerySet(q1) 不受 其他两个(q2、q3)的影响。

QuerySet 是惰性的(不会主动执行)

QuerySet 是惰性的,创建 QuerySet 的行为(语句)并不会涉及任何数据库操作。

你可以给 QuerySet 叠加许多许多过滤条件,但是 django 并不会去执行他们,直到 QuerySet 被 evaluated (检查,评估?--> 推测是 遍历、取值,翻译成取值好像更合适一点),看看下面的例子:

q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)

这看起来像执行了几次数据库操作?三次?不!其实它只执行了一次,只在执行最后一行 print(q) 的时候执行了数据库操作。通常来说,QuerySet 的结果只会在你 “访问” 它们的时候才会从数据库获取,当你执行时,QuerySet 会通过访问数据库来取值(When you do, the QuerySet is evaluated by accessing the database,翻不出来)

触发 QuerySet 让其真正执行数据库操作的几种情况
  • 迭代(for 循环遍历)
  • 加了步长的切片操作、索引取值、get、all、first 等
  • pikle 序列化时
  • 触发了 __repr__() 或者 __str__()
  • 触发了 __len__() 或者 len()
    • 如果你想获取满足条件的数据条数而不需要其他信息,可以使用 .count() 来更高效的获取数据条数
  • list() 把 QuerySet 强制转换成 list 时
  • 强转成 bool 类型或者 作为 if 条件 时
    • 如果 QuerySet 的查询结果至少有一个(数据对象),返回 True,如果没有结果,返回 False
Caching and QuerySets 缓存 和 QuerySets

每一个 QuerySet 都包含一个缓存,来最小化数据库访问次数,知道它的工作原理可以让你写出更高效的代码。

新创建的 QuerySet 的缓存(cache)是空的,QuerySet 第一次取值执行(evaluatad)的时候进行数据库查询操作,Django 会将查询结果保存到 QuerySet 的 cache 缓存中,并返回查询出来的结果集。后续取值可以复用 QuerySet 的缓存结果。

# 下面的这两行代码会走两次数据库操作,很可能他们两次得到的数据是相同的。
# 为什么我们不避免它呢? --> 很可能两次查询请求之间可能有对象被删除或者新增,会造成两次结果不一致
print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()]) # 下面的代码只会走一次数据库操作
queryset = Entry.objects.all()
print([p.headline for p in queryset]) # 真正地执行数据库操作
print([p.pub_date for p in queryset]) # 重用上一次查询出来的结果(cache)

When QuerySets are not cached 不会保存 cache 缓存的情况

QuerySet 也不总是会缓存他的查询结果,当只需要(取值)结果集中的一部分时,cache 会被检查,但如果没有被填充,则不会缓存后续查询返回的项目(but if it is not populated then the items returned by the subsequent query are not cached.),具体来说,这意味着使用数组切片或者索引限制查询结果集将不会保存缓存。

比如,每次获取一个明确的索引值都会执行一次数据库操作

# 下面的操作执行了两次数据库查询
queryset = Entry.objects.all()
print(queryset[5]) # 查询数据库
print(queryset[5]) # 又一次查询了数据库

然而,如果整个 QuerySet 已经被取值(evaluated),那么 cache 将会被缓存

# 下面的操作只执行了一次数据库查询
queryset = Entry.objects.all()
entry_list = [entry for entry in queryset] # 执行了数据库操作
print(queryset[5]) # 使用 cache
print(queryset[5]) # 使用 cache

下面是一些可以将会整个取值(evaluated)的一些案例,可以将数据存到 cache 中(让后续使用 cache,减少数据库操作次数)

[entry for entry in queryset]
bool(queryset)
entry in queryset
list(queryset)

Retrieving a single object with get() 使用 get() 只取一个数据对象

. filter() 方法返回的是一个 QuerySet ,即使他里面只有一个数据对象,如果你确定查询结果只有一个对象,你可以用 表模型类的 Manager 对象来调用 .get() 方法,往里面传入查询条件来直接获取到数据对象。

one_entry = Entry.objects.get(pk=1)

你可以在任何 查询语句 后面使用 .get() 方法,他也可以接收一些关键字参数,同样支持字段查找语法(__gt=18)。

记住这个

使用 .get().filter()[0] 有点不一样,如果没有满足条件的查询结果, .get() 会报一个 DoesNotExist 的错,这个报错是执行的表模型类的一个属性,所以,在上面的代码中,如果 Entry 对应的表中没有任何对象符合 主键 是 1,那么 django 将会报错:Entry.DoesNotExist

同样,如果有多个对象同时满足这个条件,那么 django 将会报错:MultipleObjectsReturned,这个报错也是执行的模型类的一个属性。

Other QuerySet methods 其他的 QuerySet 方法

通常情况下,当你使用 QuerySet 时会结合 filter 等链式调用,为了实现链式调用,大多数的 QuerySet 方法都会返回一个新的 QuerySet

QuerySet 类有两个公共属性你可以用于反省?(use for introspection)

ordered

如果 QuerySet 的查询结果是有序的,会返回 True,如果是无序的,会返回False

db

将会用于执行查询语句的数据库

query

可以查看当前 QuerySet 对应的 SQL 语句

Methods that return new QuerySets 返回新的 QuerySets 的方法

# 常见的几个
.filter(**kwargs) 符合条件的
.exclude(**kwargs) 不符合条件的
.annnotate(*args, **kwargs) 分组
.order_by(*fields) 排序
.reverse() 反序
.distinct(*fields) 去重
.values(*fields, **expressions*) 过滤字段
.values_list(*fields, flat=False) 过滤字段
.all()
.select_related(*field) 优化,可以把对象查出来,并附带字段,后期对象 .字段 不会再触发数据库操作
.prefetch_related(*lookups) 优化相关
.defer(*fields) 优化相关
.only(*fields) 优化相关
.select_for_update(nowait=False, skip_locked=False) # 不常见的几个
.dates(field, kind, order=‘ASC’)
.datetimes(field_name, kind, order=‘ASC’, tzinfo=None)
.none() 创建空的 QuerySet
.union(*other_qs, all=False)
.intersection(*other_qs)
.difference(*other_qs)
.extra(一堆参数) 自定义SQL(将被舍弃的方法)
.extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
.using(alias) 多个数据库时指定数据库(数据库配置可以配好几个数据库连接)
.raw(raw_query, params=None, translations=None)
.filter(**kwargs) 符合条件的

会返回一个新的 QuerySet ,里面包含的对象都是满足你给出的查询参数(条件)的,多个查询(关键字)参数以逗号间隔,对应到 SQL 语句中是 AND 连接,如果你想执行更多复杂的操作(比如 OR 或)可以使用 Q 对象

Q对象 的使用

from django.db.models import *
"""
, 间隔 Q 对象,是 and 关系 ( & 也是)
| 间隔 Q 对象,是 or 关系
~ 放在 Q 对象前面,是 ! 取反关系
""" Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
# --> SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
.exclude(**kwargs) 不符合条件的

会返回一个新的 QuerySet ,里面包含的对象都是不满足括号内指定的查询条件的,多个查询(关键字)参数以逗号间隔,参数之间是 AND 关系,其最外层逻辑是 NOT()。如果你想执行更多复杂的操作(比如 OR 或)可以使用 Q 对象

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
# 对应的SQL :SELECT ... WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello') # 同样他也支持像 filter 那样链式调用
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
# 对应的SQL :SELECT ... WHERE NOT pub_date > '2005-1-3' AND NOT headline = 'Hello'
.annnotate(*args, **kwargs) 分组-- 暂时看不懂
.order_by(*fields) 排序 -- 太长了,先放
.reverse()反序

只能对排过序的 QuerySet 使用...

.distinct(*fields) 去重
.values(fields, **expressions) 过滤字段
.values_list(*fields, flat=False) 过滤字段
.none() 创建空的 QuerySet

调用 .none() 方法会创建一个空的 QuerySet ,里面不包含任何数据对象,并且在取值时也不会执行任何数据库操作(是 EmptyQuerySet 的实例)

Entry.objects.none()
# <QuerySet []> from django.db.models.query import EmptyQuerySet isinstance(Entry.objects.none(), EmptyQuerySet)
# True # 下面两句不会执行数据库操作
print(models.Book.objects.none())
# <QuerySet []>
print(models.Book.objects.none().filter(title__contains='三国'))
# <QuerySet []>
.all()
.union(*other_qs, all=False)
.intersection(*other_qs)
.difference(*other_qs)
.select_related(*field) 优化,可以把对象查出来,并附带字段,后期对象 .字段 不会再触发数据库操作
.prefetch_related(*lookups) 优化相关
.extra(一堆参数) 自定义SQL(将被舍弃的方法)

.extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

.defer(*fields) 优化相关
.only(*fields) 优化相关
.using(alias) 多个数据库时指定数据库(数据库配置可以配好几个数据库连接)
.select_for_update(nowait=False, skip_locked=False)
.raw(raw_query, params=None, translations=None)

Methods that do not return QuerySets 不返回QuerySet 对象的方法

# 常见的几个
.get(**kwargs) 返回当个对象
.create(**kwargs)
.count()
.latest(field_name=None)
.earliest(field_name=None)
.first()
.last()
.aggregate(*args, **kwargs)
.exists()
.update(**kwargs)
.delete() # 不常见的几个
.get_or_create(defaults=None, **kwargs)
.update_or_create(defaults=None, **kwargs)
.bulk_create(objs, batch_size=None)
.in_bulk(id_list=None)
.iterator()
.as_manager()
.get(**kwargs) 返回当个对象
.create(**kwargs)
.count()
.first()
.last()
.aggregate(*args, **kwargs)
.exists()
.update(**kwargs)
.delete()
.latest(field_name=None)
.earliest(field_name=None)

Aggregation functions 聚合函数

field-lookups 字段查找(字段查询条件,双下划线查询)

字段查找(field-lookups)对应的是 SQL 语句中的 WHERE 条件,一般放在 QuerySet 对象的 filter() 、exclude()、get() 方法中作为条件

常见形式

注意点

不同数据库对这些方法支持不同,django orm 对应不同数据库也能翻译成不同的 SQL 语句

  • sqlite 对日期类型支持不友好、数据(字符串)大小写不敏感(忽略大小写)
  • python 对浮点数精度不敏感(price=66.66 --> 可能有这么一条记录,但它却匹配不到(python(好像是 sqlite吧?) 把它变成了 66.651556464))

书写格式

field__lookuptype --> price_gt (两个下划线)

比如

# 省略一些要导入的文件
Entry.objects.filter(pub_date__lte='2006-01-01') # 翻译成对应的 SQL 语句就是:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01'; # pub_date__lte='2006-01-01' --> pub_date <= '2006-01-01'

lookuptype 查找类型分类整理

关系比较类

__gt
# 字段大于...
Entry.objects.filter(id__gt=4)
# --> SELECT ... WHERE id > 4; __gte
# 大于等于 __lt
# 小于 __lte
# 小于等于 __isnull
# 字段是否为空
Entry.objects.filter(pub_date__isnull=True)
# --> SELECT ... WHERE pub_date IS NULL;

模糊匹配类、正则

精准匹配直接就是 = / exact

# --------- 是否包含 --------------
__contains
# 字段值是否包含 __
Entry.objects.get(headline__contains='Lennon')
# --> SELECT ... WHERE headline LIKE '%Lennon%'; __icontains
# 字段值是否包含 __ ,忽略大小写的包含
Entry.objects.get(headline__icontains='Lennon')
# --> SELECT ... WHERE headline ILIKE '%Lennon%'; # --------- 以 ... 开头 或 结尾 --------------
__startswith
# 字段以 __ 开头
Entry.objects.filter(headline__startswith='Lennon')
# --> SELECT ... WHERE headline LIKE 'Lennon%'; __istartswith
# 字段以 __ 开头,忽略大小写
Entry.objects.filter(headline__istartswith='Lennon')
# --> SELECT ... WHERE headline ILIKE 'Lennon%'; __endswith
# 字段以 __ 结尾
Entry.objects.filter(headline__endswith='Lennon')
# --> SELECT ... WHERE headline LIKE '%Lennon'; __iendswith
# 字段以 __ 结尾,忽略大小写
Entry.objects.filter(headline__iendswith='Lennon')
# --> SELECT ... WHERE headline ILIKE '%Lennon' # --------- 全文检索 --------------
__search
# 全文检索(django 1.10 开始有改动)
Entry.objects.filter(headline__search="+Django -jazz Python")
# --> SELECT ... WHERE MATCH(tablename, headline) AGAINST (+Django -jazz Python IN BOOLEAN MODE); # --------- 正则相关 --------------
__regex
# 正则匹配
Entry.objects.get(title__regex=r'^(An?|The) +')
# --> SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; # -- MySQL,对于这个字段查询,django orm 对应不同的 数据库 会解析成不同的 SQL 语句 __iregex
# 忽略大小写的正则匹配
# 案例
Entry.objects.get(title__iregex=r'^(an?|the) +')
# -->SELECT ... WHERE title REGEXP '^(an?|the) +';

范围类

__in
# 字段的值在不在给定的列表范围内
Entry.objects.filter(id__in=[1, 3, 4])
# --> SELECT ... WHERE id IN (1, 3, 4); # 补充:也可以使用会动态的查询 QuerySet 作为列表
inner_qs = Blog.objects.filter(name__contains='Cheddar')
entries = Entry.objects.filter(blog__in=inner_qs)
# --> SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%') __range
# 可以比较日期时间、数字范围、字符(串?没验证)范围
import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))
# --> SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';

日期时间类

__date
# 匹配 datetime 类型字段,会将传入的值转换为日期,然后搭配 关系类的字段查找(field-lookups)进行比较
Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
# --> ... __year
# 匹配 datetime、date 类型字段,直接指定精确的哪一年
Entry.objects.filter(pub_date__year=2005)
# --> SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
Entry.objects.filter(pub_date__year__gte=2005)
# --> SELECT ... WHERE pub_date >= '2005-01-01'; __month
# 匹配 datetime、date 类型字段,范围是 1 (January) --- 12 (December),语句视数据库引擎而定
Entry.objects.filter(pub_date__month=12)
# --> SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12';
Entry.objects.filter(pub_date__month__gte=6)
# --> SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6'; __day
# 匹配 datetime、date 类型字段,当月的第几天
Entry.objects.filter(pub_date__day=3)
# --> SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3';
Entry.objects.filter(pub_date__day__gte=3)
# --> SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';
# Note this will match any record with a pub_date on the third day of the month, such as January 3, July 3, etc. __week
# 匹配 datetime、date 类型字段,当年的第几周(1-52/53,平闰年不同)
# django 1.11 中新增的
Entry.objects.filter(pub_date__week=52)
Entry.objects.filter(pub_date__week__gte=32, pub_date__week__lte=38)
# --> ...
# return the week number (1-52 or 53) according to ISO-8601, i.e., weeks start on a Monday and the first week starts on or before Thursday. __week_day
# 匹配 datetime、date 类型字段 范围:1 (Sunday) -- 7 (Saturday)
Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)
# --> ... __time
# 匹配 datetime、time 类型字段的 minute, django 1.11 中新增的
Entry.objects.filter(pub_date__time=datetime.time(14, 30))
# 实现方式取决于数据库引擎(暂时没有例子) __hour
# 匹配 datetime、time 类型字段的 minute,范围 0-23
Event.objects.filter(timestamp__hour=23)
# --> SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23'; __minute
# 匹配 datetime、time 类型字段的 minute,范围 0-59
Event.objects.filter(timestamp__minute=29)
# --> SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29'; __second
# datetime、time 类型字段相关的,看不太懂
Event.objects.filter(timestamp__second=31)
# --> SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31';
# 文档:
# For datetime and time fields, an exact second match. Allows chaining additional field lookups. Takes an integer between 0 and 59.
# For datetime fields, when USE_TZ is True, values are converted to the current time zone before filtering.

自定义字段查找(custom field-lookups)

单词

lookuptype		查找类型
refine 细分、精细化(缩小范围)
brand-new 崭新的
in no way 绝对不
separate 独立的...
stored 存储
reuse 重用
an additional criteria 附加条件
the act of ...的行为
involve 涉及
stack ... together 将...叠加在一起
evaluated (检查,评估?--> 推测是 遍历、取值,翻译成取值好像更合适一点)
alias 别名

特别点

05-11 09:43
查看更多