zoukankan      html  css  js  c++  java
  • ORM之基础操作进阶

    复制代码
    一、外键自关联(一对多)
    1、建表
    # 评论表
    class Comment(models.Model):
        id = models.AutoField(primary_key=True)
        content = models.CharField(max_length=255)
        push_time = models.DateTimeField(auto_now_add=True)
        # 父评论:自关联,一个评论可以没有父评论所以null=True
        pcomment = models.ForeignKey(to='self', null=True)
    
        def __str__(self):
            return self.content
    
    2、表数据
    2、操作 1.找到id等于2的那个评论的所有子评论(跨表查询) ret = Comment.objects.filter(pcomment__id=2) print(ret) 2.方法2(直接查询) ret = Comment.objects.filter(pcomment_id=2) print(ret) 3.方法3 ret = Comment.objects.filter(id=2).values('comment__id') print(ret) 二、多对多自关联 1、建表 class Person(models.Model): name = models.CharField(max_length=12) friends = models.ManyToManyField(to='self', symmetrical=False) def __str__(self): return self.name 2、注意 在多对多的自关联时,如果需要反向查找,则需要添加symmetrical这个字段, 指定内部是否创建反向操作的字段,默认为True,需要反向查找则改成False 3、操作 1.找到小明的朋友(正向找) ret = Person.objects.filter(name='小明').values('friends__name') print(ret) 2.朋友是小明的那个人(反向找) ret = Person.objects.get(name='小明').person_set.all() print(ret) 或者 ret = Person.objects.filter(name='小明').values('person__name') print(ret)


    补充:
    create(关键字参数)
      创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。
      publisher.objects.create(id=1, name="...")

    三、补充的几个方法
    1、这几个方法适用于一对多和多对多的情况,一对多的时候,在"一"这张表才有下面这些方法,多对多的时候,两张表跨到第三张表时都有下面这些方法, 且只是对象才能使用(queryset列表不能使用),就是obj.get()、obj.first()、obj.last()才能使用,
    需要注意的是:'一'这张表使用下面这些方法时,参数是对象,而多对多的第三张表使用下面这些方法时,参数是id
    它存在于下面两种情况: 外键关系的反向查询 多对多关联关系 简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法
    2、方法
    1.add(位置参数) 把指定的model对象添加到关联对象集中。 但是一对多中add里面的实参(位置参数)是对象 而多对多中add里面的实参(位置参数)可以是id 2.set([列表]) 更新model对象的关联对象。 但是一对多中set里面的实参(列表)是对象 而多对多中add里面的实参(列表)可以是id 3.remove() 从关联对象集中移除执行的model对象 4.clear() 从关联对象集中移除一切对象。 注意: 对于ForeignKey对象,clear()和remove()方法仅在null=True时存在。 举个例子: ForeignKey字段没设置null=True时, class Book(models.Model): title = models.CharField(max_length=32) publisher = models.ForeignKey(to=Publisher) 没有clear()和remove()方法 >>> models.Publisher.objects.first().book_set.clear() Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'RelatedManager' object has no attribute 'clear' 当ForeignKey字段设置null=True时, class Book(models.Model): name = models.CharField(max_length=32) publisher = models.ForeignKey(to=Class, null=True) 此时就有clear()和remove()方法 >>> models.Publisher.objects.first().book_set.clear() 5.注意 对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。 6.建表 class MyClass(models.Model): cname = models.CharField(max_length=12) class Student(models.Model): sname = models.CharField(max_length=12) myclass = models.ForeignKey(to='MyClass') def __str__(self): return self.sname class Teacher(models.Model): tname = models.CharField(max_length=12) myclass = models.ManyToManyField(to='MyClass') 7.例子 1.一对多 # 查询id=2的班级的所有学生 ret = MyClass.objects.get(id=2).student_set.all() print(ret) # id=2的班级的所有老师 ret = MyClass.objects.get(id=2).teacher_set.all() print(ret) # 给id=1的班级添加一个id=4的学生 student_obj = Student.objects.get(id=4) MyClass.objects.get(id=1).student_set.add(student_obj) ret = MyClass.objects.get(id=1).student_set.all() print(ret) # 把所有学生都绑定到id=1的班级中 MyClass.objects.get(id=1).student_set.set(Student.objects.all()) ret = MyClass.objects.get(id=1).student_set.all() print(ret) 2.多对多 # 给id=2的老师添加一个id=4的班级 Teacher.objects.get(id=2).myclass.add(4)
      # 给id=3的班级添加一个id=1的老师
      Myclass.objects.get(id=3).teacher_set.add(1)  
      
      # 把id=3的班级的老师修改为id=2和id=3的老师
      Myclass.objects.get(id=3).teacher_set.set([2, 3])
    # 从id=2的老师的中移除2班 Teacher.objects.get(id=2).myclass.remove(2) # 清空id=2的老师的班级 Teacher.objects.get(id=2).myclass.clear() # 给id=2的老师新增一门课:生理课 Teacher.objects.get(id=2).myclass.create(cname='生理课') 四、如何在Django终端打印SQL语句 在Django项目的settings.py文件中,在最后复制粘贴如下代码: LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } } 即为你的Django项目配置上一个名为django.db.backends的logger实例即可查看翻译后的SQL语句。 五、基于对象的跟新(save)和基于QuerySet的update跟新的区别 # 把南山出版社的ceo改成小红 # 1. 基于对象的修改(会跟新所有字段,效率慢) publisher_obj = Publisher.objects.get(name='南山出版社') publisher_obj.ceo = '小红' publisher_obj.save() # 2. 基于QuerySet的update跟新(只跟新指定的字段,效率快于基于对象的修改) Publisher.objects.filter(name='南山出版社').update(ceo='小勾') 六、聚合查询
    注意:ORM的聚合函数一定要搭配aggregate或者annotate使用
    6-1、aggregate
    1、介绍 aggregate()是QuerySet的一个终止子句,意思是说,它返回一个包含一些键值对的字典。 键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。 用到的内置函数: from django.db.models import Avg, Sum, Max, Min, Count 2、例子 from django.db.models import Avg, Sum, Max, Min, Count # 求所有书中价格最高的书 ret = Book.objects.all().aggregate(Max('price')) print(ret) # 为聚合值指定一个名称,返回指定的key值 ret = Book.objects.all().aggregate(max_price=Max('price')) print(ret) # 生成不止一个聚合,求所有书的平均价格和最小的价格 ret = Book.objects.all().aggregate(avg_price=Avg('price'), min_price=Min('price')) print(ret) 6-2、annotate分组查询 1、介绍 values/values_list 对应 SQL语句 select 部分 filter 对应 SQL语句 where 部分 2、ORM中分组使用annotate 1. annotate前面查询(values)的是什么,就按什么分组,没有values就默认按id分组,而id是唯一的,因此不写values等于没有分组 2. annotate中要写上分组之后要做的事情 3、建表 class Employee(models.Model): name = models.CharField(max_length=12) age = models.IntegerField() salary = models.IntegerField() province = models.CharField(max_length=12) dept = models.CharField(max_length=12) 4、例子 from django.db.models import Avg, Sum, Max, Min, Count # 使用ORM查询每个部门的平均工资 ret = Employee.objects.values('dept').annotate(avg=Avg('salary')) print(ret) ret = Employee.objects.annotate(avg=Avg('salary')).values('dept', 'avg') print(ret) # 等于没有分组 # 每个部门的平均年龄 ret = Employee.objects.values('dept').annotate(Avg('age')).values_list('dept', 'avg_age') print(ret) 4、多表使用分组 class Publisher(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16) addr = models.TextField() # 成立日期:对应Python中的datetime.date类型 date = models.DateField() def __str__(self): return self.name class Book(models.Model): # 书名 title = models.CharField(max_length=16) # 价格:最多显示6个数字,小数位有2个 price = models.DecimalField(max_digits=6, decimal_places=2) # ISBN:书籍的唯一编号 isbn = models.CharField(max_length=20, unique=True) # 外键关联出版社,db_constraint=False不在数据库中建立约束 publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, related_name='books') def __str__(self): return self.title class Author(models.Model): name = models.CharField(max_length=12) # 性别,choice选项用的是本模块定义的常量,默认选保密 gender = models.SmallIntegerField(choices=((1, ''), (2, ''), (3, '保密')), default=3) # 手机号,唯一约束 phone = models.CharField(max_length=11, unique=True) # 邮箱 email = models.EmailField() # 多对多关联书籍 books = models.ManyToManyField(to='Book', related_name='authors') 例子: from django.db.models import Avg, Sum, Max, Min, Count # 求每个出版社出版的书的平均价格
    # 基于Book ret = Book.objects.values('publisher_id').annotate(avg_price=Avg('price')).values_list('publisher__name', 'avg_price') print(ret)
    # 基于Publisher
    ret = Publisher.objects.annotate(avg_price=Avg(books__price)).values('name', 'avg_price')
    print(ret)
    # 求每个出版社出版的书的数量 ret = Book.objects.values('publisher_id').annotate(count=Count('id')).values_list('publisher__name', 'count') print(ret) 七、Q查询 filter()等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。 同样是上面书籍出版社那个数据库 例如: 1、或 | from django.db.models import Q # 查询书的价格大于100或者作者是小明的书 ret = Book.objects.filter(Q(price__gt=100) | Q(authors__name='小明')) print(ret) 2、且 & # 原生的 且 # 查询书的价格大于100且作者是小明的书 ret = Book.objects.filter(price__gt=100, authors__name='小明') print(ret) # Q对象的 且 from django.db.models import Q ret = Book.objects.filter(Q(price__gt=100) & Q(authors__name='小明')) print(ret) 3、非 ~ from django.db.models import Q ret = Book.objects.filter(Q(price__gt=100) & ~Q(authors__name='小明')) print(ret) 4、混合使用: 查询函数可以混合使用Q对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q对象)都将进行'END'运算, 但是,如果出现Q对象,它必须位于所有关键字参数的前面。 # 查询出版社id是1或者2,且作者名字中有小的书 ret = Book.objects.filter(Q(publisher_id=1) | Q(publisher_id=2), authors__name__contains='') print(ret)
    5、Q的拓展使用方法
    Q查询的两种写法
        # Q原始写法
        data = data.filter(
            Q(name__icontains=query_value)|Q(qq__icontains=query_value)|Q(qq_name__icontains=query_value)
        )
        或者
        data = data.filter(
            Q(('name__icontains', query_value))|Q(('qq__icontains', query_value))|Q(('qq_name__icontains', query_value))
        )
    
    
        # Q使用对象的写法
        q = Q()  # 实例一个Q对象
        q.connector = 'OR'  # 连接符,OR AND
        q.children.append(Q(('name__icontains', query_value)))  # Q查询里面的实参是一个元组,元组第一个元素是查询的字段(字符串形式)
        q.children.append(Q(('qq__icontains', query_value)))  # 元组第二个元素是需要匹配的值
        q.children.append(Q(('qq_name__icontains', query_value)))
    
        data = data.filter(q)
    View Code
    
    
    
    
    
    八、F查询
    1、字段在原基础上跟新
    1.1跟新字段的值(数字) 例如:
    from django.db.models import F # F用来在字段原来的基础上进行操作 Book.objects.all().update(price=F('price')+20) 进行了上面的操作后,数据库中book表所有的price字段的值全部加了20, 比如本来90的,现在就110
    1.2拓展:跟新字段的值(字符串) from django.db.models import F # 给每本书的名字加'新款'(注意:一般在实际中不应该这么做,这里只是用于了解F对象) from django.db.models.functions import Concat # 字符串拼接 from django.db.models import Value # 把字符串转变成F对象可以操作的变量 Book.objects.all().update(title=Concat(F('title'), Value('新款')))


    2、同一条记录不同字段之间的比较
    # 找出卖出数大于收藏数的书籍
    Book.objects.filter(sale_num__gt=F('fav_num')) # sale_num是这本书的卖出的数量,fav_num是这本书收藏的次数
    九、事务 在事务代码块内的代码,全部都执行成功后,数据库中才会进行修改 只要存在一句错误的语句,全部代码都不会生效 一般使用在安全性需要很高的场景,比如,银行的转账, 为了避免一方转账后,中途出现断电,服务器异常等情况,导致另一方没收到转账, 而转账方的钱却减少了
    from django.db import transaction with transaction.atomic(): # 创建一本书 Book.objects.create(title='嘿嘿书', price=10.99, isbn='213dsxc', publisher_id=1) # 把id=100的书名修改为哈哈书 book_obj = Book.objects.get(id=100) # 没有id=100的书,这里会报错,结果是整体都不生效,数据库不会有任何修改 book_obj.title = '哈哈书' book_obj.save() 十、去重distinct ret = Book.objects.all().values('publisher__name').distinct() print(ret) 十一、Django ORM执行原生SQL(拓展了解) 1、raw 1. 查询自己这个表 ret = Book.objects.raw('select * from app04_book') # raw里面写原生的SQL语句 print(ret) for i in ret: print(i.title, i.price) 2. 查询别的表 在Book表查询publisher表,select必须要写上publisher的主键,能使用的也只有主键,如果要使用其他字段,就在主键后写上其他字段 ret = Book.objects.raw('select id, name from app04_publisher') for i in ret: print(i.id, i.name) 拓展知识:在ORM中,使用的是懒查询,就是当你没有使用这个结果的时候,它是不会去数据库帮你查询的, 比如上面的 ret = Book.objects.raw('select * from app04_book') 当你print这个结果的时候,并不会有内容的 因为ORM没有看到你使用这个ret,它不会去数据库帮你查询的,而当你把ret进行循环的时候,ORM知道你要使用了,它才会 真正地去数据库帮你进行操作并返回结果给你。 2、直接执行自定义SQL 有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。 我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。 from django.db import connection, connections connection获取默认的数据库 connections当有多个数据库的时候,使用关键字获取指定的数据库 connections['default'] 例如: from django.db import connection, connections cursor = connection.cursor() cursor.execute('select * from app04_book') ret = cursor.fetchone() print(ret) 十二、QuerySet方法大全
    def all(self)
        # 获取所有的数据对象
    
    def filter(self, *args, **kwargs)
        # 条件查询
        # 条件可以是:参数,字典,Q
    
    def exclude(self, *args, **kwargs)
        # 条件查询
        # 条件可以是:参数,字典,Q
    
    def select_related(self, *fields)
        性能相关:表之间进行join连表操作,一次性获取关联的数据。
    
        总结:
        1. select_related主要针一对一和多对一关系进行优化。
        2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
    
    def prefetch_related(self, *lookups)
        性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
    
        总结:
        1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
        2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。
    
    def annotate(self, *args, **kwargs)
        # 用于实现聚合group by查询
    
        from django.db.models import Count, Avg, Max, Min, Sum
    
        v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
        # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
    
        v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
        # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
    
        v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
        # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
    
    def distinct(self, *field_names)
        # 用于distinct去重
        models.UserInfo.objects.values('nid').distinct()
        # select distinct nid from userinfo
    
        注:只有在PostgreSQL中才能使用distinct进行去重
    
    def order_by(self, *field_names)
        # 用于排序
        models.UserInfo.objects.all().order_by('-id','age')
    
    def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
        # 构造额外的查询条件或者映射,如:子查询
    
        Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
        Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
        Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
        Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
    
     def reverse(self):
        # 倒序
        models.UserInfo.objects.all().order_by('-nid').reverse()
        # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序
    
    
     def defer(self, *fields):
        models.UserInfo.objects.defer('username','id')
        或
        models.UserInfo.objects.filter(...).defer('username','id')
        #映射中排除某列数据
    
     def only(self, *fields):
        #仅取某个表中的数据
         models.UserInfo.objects.only('username','id')
         或
         models.UserInfo.objects.filter(...).only('username','id')
    
     def using(self, alias):
         指定使用的数据库,参数为别名(setting中的设置)
    
    
    ##################################################
    # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
    ##################################################
    
    def raw(self, raw_query, params=None, translations=None, using=None):
        # 执行原生SQL
        models.UserInfo.objects.raw('select * from userinfo')
    
        # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
        models.UserInfo.objects.raw('select id as nid from 其他表')
    
        # 为原生SQL设置参数
        models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
    
        # 将获取的到列名转换为指定列名
        name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
        Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
    
        # 指定数据库
        models.UserInfo.objects.raw('select * from userinfo', using="default")
    
        ################### 原生SQL ###################
        from django.db import connection, connections
        cursor = connection.cursor()  # cursor = connections['default'].cursor()
        cursor.execute("""SELECT * from auth_user where id = %s""", [1])
        row = cursor.fetchone() # fetchall()/fetchmany(..)
    
    
    def values(self, *fields):
        # 获取每行数据为字典格式
    
    def values_list(self, *fields, **kwargs):
        # 获取每行数据为元祖
    
    def dates(self, field_name, kind, order='ASC'):
        # 根据时间进行某一部分进行去重查找并截取指定内容
        # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
        # order只能是:"ASC"  "DESC"
        # 并获取转换后的时间
            - year : 年-01-01
            - month: 年-月-01
            - day  : 年-月-日
    
        models.DatePlus.objects.dates('ctime','day','DESC')
    
    def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
        # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
        # kind只能是 "year", "month", "day", "hour", "minute", "second"
        # order只能是:"ASC"  "DESC"
        # tzinfo时区对象
        models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
        models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
    
        """
        pip3 install pytz
        import pytz
        pytz.all_timezones
        pytz.timezone(‘Asia/Shanghai’)
        """
    
    def none(self):
        # 空QuerySet对象
    
    
    ####################################
    # METHODS THAT DO DATABASE QUERIES #
    ####################################
    
    def aggregate(self, *args, **kwargs):
       # 聚合函数,获取字典类型聚合结果
       from django.db.models import Count, Avg, Max, Min, Sum
       result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
       ===> {'k': 3, 'n': 4}
    
    def count(self):
       # 获取个数
    
    def get(self, *args, **kwargs):
       # 获取单个对象
    
    def create(self, **kwargs):
       # 创建对象
    
    def bulk_create(self, objs, batch_size=None):
        # 批量插入
        # batch_size表示一次插入的个数
        objs = [
            models.DDD(name='r11'),
            models.DDD(name='r22')
        ]
        models.DDD.objects.bulk_create(objs, 10)
    
    def get_or_create(self, defaults=None, **kwargs):
        # 如果存在,则获取,否则,创建
        # defaults 指定创建时,其他字段的值
        obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
    
    def update_or_create(self, defaults=None, **kwargs):
        # 如果存在,则更新,否则,创建
        # defaults 指定创建时或更新时的其他字段
        obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
    
    def first(self):
       # 获取第一个
    
    def last(self):
       # 获取最后一个
    
    def in_bulk(self, id_list=None):
       # 根据主键ID进行查找
       id_list = [11,21,31]
       models.DDD.objects.in_bulk(id_list)
    
    def delete(self):
       # 删除
    
    def update(self, **kwargs):
        # 更新
    
    def exists(self):
       # 是否有结果
    方法大全
    
    
    
    举几个例子:
    # only:将指定的字段查询加载出来,后续再访问指定的字段就不需要再查询数据库
    ret = Book.objects.all().only('title')  # 拿到所有书的对象列表
    for i in ret:
        print(i.title)  # 访问指定的字段title不需要再去查询数据库
    
    for i in ret:
        print(i.price)  # 访问不是指定的字段,每一次都去查一次数据库
    
    
    
    # defer:将除了指定的字段查询加载出来,后续再访问指定的字段就不需要再查询数据库(only的反义词)
    ret = Book.objects.all().defer('title')
    for i in ret:
        print(i.title)  # 访问指定的字段title,每一次都需要去查询数据库
    
    for i in ret:
        print(i.price)  # 访问不是指定的字段,不需要再查询数据库
    
    
    # bulk_create:一次SQL语句批量创建
    from datetime import date
    obj = (Publisher(name='第%s出版社' %(i), addr='中国', date=date.today()) for i in range(100))
    Publisher.objects.bulk_create(obj)
    
    
    # select_related:把id=2的书的信息和它关联的出版社的信息一起查询出来。
    ret = Book.objects.filter(id=2).select_related('publisher')
    print(ret[0].publisher.name)
    复制代码
    aggregate
  • 相关阅读:
    Validation failed for one or more entities. See 'EntityValidationErrors' property for more details
    Visual Studio断点调试, 无法监视变量, 提示无法计算表达式
    ASP.NET MVC中MaxLength特性设置无效
    项目从.NET 4.5迁移到.NET 4.0遇到的问题
    发布网站时应该把debug设置false
    什么时候用var关键字
    扩展方法略好于帮助方法
    在基类构造器中调用虚方法需谨慎
    ASP.NET MVC中商品模块小样
    ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现
  • 原文地址:https://www.cnblogs.com/yidashi110/p/10091971.html
Copyright © 2011-2022 走看看