Django之ORM连表操作

一 1对1(OneToOneField)

简单代码如下:

class UserGroup(models.Model):
    """部门"""
    department = models.CharField(max_length=32)

class UserInfo(models.Model):
    """员工信息"""
    uid = models.AutoField(primary_key=True)            # 若无指定,Django为默认生成以id命名的一列自增数据
    username = models.CharField(max_length=32)          # 必须指定长度
    ug = models.ForeignKey("UserGroup",on_delete=models.CASCADE,)       # 生成数据库后,列名称变更为ug_‘外键表的id’

class UserDeital(models.Model):
    '''员工详细'''
    joindata = models.DateField()
    age = models.IntegerField(default=18)
    email = models.EmailField()
    phone = models.IntegerField(max_length=12)
    user = models.OneToOneField('UserInfo',on_delete=models.CASCADE)
View Code

# 正向操作
deital_obj = models.UserDeital.objects.filter(age=24).first()
print(deital_obj.user.username)

# 反向操作
user_obj = models.UserInfo.objects.filter(username='张三1').first()
print(user_obj.userdeital.email,user_obj.userdeital.phone)

更多操作可以参考下面的内容。

二 1对多(ForeignKey)

何为一对多?如果A表中的1条数据对应B表中N条数据,两表之间就是1对多关系;在1对多关系中 A表就是主表,B表为子表,ForeignKey字段就建在子表

我们还是结合之前的例子(去掉password字段,加入jiondata字段),,代码如下:

from django.db import models

class UserGroup(models.Model):
    """部门"""
    department = models.CharField(max_length=32)

class UserInfo(models.Model):
    """员工信息"""
    uid = models.AutoField(primary_key=True)            #若无指定,Django为默认生成以id命名的一列自增数据
    username = models.CharField(max_length=32)          #必须指定长度
    password = models.CharField(max_length=64)
    age = models.IntegerField(default=18)
    joindata = models.DateField()
    ug = models.ForeignKey("UserGroup",on_delete=models.CASCADE,)       # 生成数据库后,列名称变更为ug_‘外键表的id’
View Code

我们从以上表结构可以看出:usergroup为主表,userinfo为子表。

2.1 增

# 方式一:直接通过字表指定ug_id添加,前提是主表里面必须有该数据
models.UserInfo.objects.create(username="张三1", age=22, joindata="2015-6-6", ug_id=1)

# 方式二:获取主表对象某个后,通过create()添加
depart_obj = models.UserGroup.objects.filter(department="人力资源部").first()
models.UserInfo.objects.create(username="张三1", age=22, joindata="2015-6-6", ug=depart_obj)   #添加对象时为ug,而不是ug_id

# 方式三:获取主表对象某个后,通过save()添加
depart_obj = models.UserGroup.objects.get(department="人力资源部")  # 只有一个的时候用get,拿到的直接就是一个对象
user_obj = models.UserInfo(username="张三1", age=22, joindata="2015-6-6", ug=depart_obj)
user_obj.save()

2.2 查

我们查询到的数据类型本质上都是queryset类型(类似于列表),内部有三种表现形式:对象、字典、元组。

正向操作

# 1.以对象的形式进行查询(通过“对象.外键字段.关联表字段”),该方式取的时候跨表
obj = models.UserInfo.objects.all().first()
print(obj)				# UserInfo object (1)					
print(obj.username, obj.age, obj.ug.department)    # 张三1 22 人力资源部

# 2.以字典的形式进行查询(关键字:values),该方式查的时候跨表
dict1 = models.UserInfo.objects.filter(uid__gt=3).values('username', 'age', 'ug__department')
print(dict1)					# <QuerySet [{'ug__department': 'IT', 'age': 23, 'username': '张三4'}, ...]>
for item in dict1:
	print(item['username'], item['age'], item['ug__department'])   # 张三4 23 IT,...
# 筛选
obj1 = models.UserInfo.objects.filter(ug__department='IT').values('username', 'age', 'ug__department')
print(obj1)						# <QuerySet [{'ug__department': 'IT', 'age': 25, 'username': '张三3'}, ...]>

# 3.以元组的形式进行查询(关键字:values_list),该方式查的时候跨表
list1 = models.UserInfo.objects.filter(uid__gt=3).values_list('username', 'age', 'ug__department')
print(list1)					# <QuerySet [('张三4', 23, 'IT'), ('张三5', 28, '公关部')]>
for item in list1:
	print(item[0], item[1], item[2])				# 张三4 23 IT,...
# 筛选
obj2 = models.UserInfo.objects.filter(ug__department='IT').values_list('username', 'age', 'ug__department')
print(obj2)						# <QuerySet [('张三3', 25, 'IT'), ('张三4', 23, 'IT')]>

反向操作

# 1.以对象的形式进行查询(通过“表名小写_set.all()”)
obj = models.UserGroup.objects.all().first()
print(obj.id, obj.department)			# 1 人力资源部
for row in obj.userinfo_set.all():
	print(row.username, row.age)		# 张三1 22, 张三2 19
# 筛选
result = models.UserGroup.objects.all()
for item in result:
	print(item.department, item.userinfo_set.filter(username='张三1'))   
	# 人力资源部 <QuerySet [<UserInfo: UserInfo object (1)>]>,IT <QuerySet []>,公关部 <QuerySet []>

# 2.以字典的形式进行查询,该方式查的时候跨表
obj1 = models.UserGroup.objects.values("id", 'department', 'userinfo')  # 通过‘小写表名’获取id
print(obj1)			# <QuerySet [{'department': '人力资源部', 'id': 1, 'userinfo': 1}, ...]>
obj2 = models.UserGroup.objects.values("id", 'department', 'userinfo__username')  # 通过‘小写表名__列名’获取相应数据
print(obj2)			# <QuerySet [{'department': '人力资源部', 'id': 1, 'userinfo__username': '张三1'}, ...]>
# 筛选
obj3 = models.UserGroup.objects.filter(userinfo__age=22).values('id', 'department', 'userinfo__username')
print(obj3)			# <QuerySet [{'department': '人力资源部', 'id': 1, 'userinfo__username': '张三1'}]>

# 3.以元组的形式进行查询,该方式查的时候跨表
obj4 = models.UserGroup.objects.values_list("id", 'department', 'userinfo')  # 通过‘小写表名’获取id
print(obj4)			# <QuerySet [(1, '人力资源部', 1), (1, '人力资源部', 2), (2, 'IT', 3), (2, 'IT', 4), (3, '公关部', 5)]>
obj5 = models.UserGroup.objects.values_list("id", 'department', 'userinfo__username')  # 通过‘小写表名__列名’获取相应数
print(obj5)			# <QuerySet [(1, '人力资源部', '张三1'),...]>
# 筛选
obj6 = models.UserGroup.objects.filter(userinfo__age=22).values_list('id', 'department', 'userinfo__username')
print(obj6)			# <QuerySet [(1, '人力资源部', '张三1')]>

补充:我们可以在ForeignKey字段中加入related_name属性,指定通过该属性值进行反向关联。注意:该方式与小写表名方式不能共存

总结:

正向:

  • 对象.外键字段.关联表字段
  • values(外键字段__关联表字段)
  • values_list(外键字段__关联表字段)
  • filter(外键字段__关联表字段)

反向:

  • 对象:对象.小写表名_set.all()  /  对象:对象.related_name属性值.all()
  • value、value_list:'小写表名' —> 获取id  /  'related_name属性值' —> 获取id
  • value、value_list:'小写表名__关联表字段' —> 获取相应字段数据  /  'related_name属性值__关联表字段' —> 获取相应字段数据
  • filter(小写表名__关联表字段)  /  filter(related_name属性值__关联表字段) 

2.3 1对多自关联( 由原来的2张表,变成一张表)

想象有第二张表,关联自己表中的行

image

举个例子,在网页上对新闻做评论的结构:

image

这时我们就需要1对多自关联,表结构如下:

class Comment(models.Model):
    """评论表"""
    news_id = models.IntegerField()             # 新闻ID
    content = models.CharField(max_length=32)   # 评论内容
    user = models.CharField(max_length=32)      # 评论者
    reply = models.ForeignKey('Comment',null=True,blank=True)   # 回复ID

 我们向数据库插入数据形式如下:

image

需要注意的是:reply ID要么为空,要么填入已经存在的评论id,它是针对某个评论,而不是针对某个人。

三 多对多

何为多对多?如果A表中的1条数据对应B表中N条数据,且B表的1条数据也对应A表中N条数据,两表之间就是双向1对多关系,即称为多对多关系

3.1 自定义第三张表

接着上面员工部门的例子,我们在新增两个数据表,具体如下:

class HobbyList(models.Model):
    '''爱好清单'''
    hobby = models.CharField(max_length=16)

class U2H(models.Model):
    user =  models.ForeignKey("UserInfo",on_delete=models.CASCADE,)
    hobby = models.ForeignKey("HobbyList",on_delete=models.CASCADE,)
    # 根据实际情况,我们可以加上联合唯一索引
    class Meta:
        unique_together = [
            ('user','hobby'),
        ]

以上我们增加爱好清单,然后用另外一张数据表类存储员工与爱好之间的关系,以实现爱好与员工之间形成多对多的关系

员工表:

image

爱好表:

image

# 先查询或创建一个员工对象:
user_obj = models.UserInfo.objects.filter(username="张三1").first()
# 创建需要关联的爱好
hobby_obj1 = models.HobbyList.objects.filter(hobby="篮球").first()
hobby_obj2 = models.HobbyList.objects.filter(hobby="台球").first()
# 通过for循环进行添加
obj_list = [hobby_obj1,hobby_obj2]
for obj in obj_list:
    models.U2H.objects.create(user=user_obj,hobby=obj)

第三张关系表:

image

# 获取张三1的爱好清单
# 方式一:通过userinfo表进行反向查找u2h,再正向查找
obj = models.UserInfo.objects.filter(username='张三1').first()
obj_list = obj.u2h_set.all()
for i in obj_list:
	print(i.hobby.hobby)      # 篮球、台球

# 方式二:通过u2h表查询
obj = models.U2H.objects.filter(user__username='张三1').all()
print(obj)          # <QuerySet [<U2H: U2H object (1)>, <U2H: U2H object (2)>]>
for i in obj:
	print(i.hobby.hobby)          # 篮球、台球
# 注意:这样进行查询性能不好,在循环时需要重新发sql请求再进行查询另外一张表中的数据

# 方式三:(推荐)
obj = models.U2H.objects.filter(user__username='张三1').values('hobby__hobby')
print(obj)          # <QuerySet [{'hobby__hobby': '篮球'}, {'hobby__hobby': '台球'}]>
for i in obj:
	print(i['hobby__hobby'])  # 篮球、台球
# 这样我们进行了优化,不会重复发送sql请求,当然我们也可以使用values_list,只是取到的结果表现形式不同

# 方式四:查询主动做连表(推荐)
obj = models.U2H.objects.filter(user__username='张三1').select_related('hobby')
print(obj)          # <QuerySet [<U2H: U2H object (1)>, <U2H: U2H object (2)>]>
for i in obj:
	print(i.hobby.hobby)      # 篮球、台球
# select_related('hobby') 相当于inner join先连成一张表再进行查询

3.2 通过ManyToManyField生成第三张表

示例如下:

image

代码如下:

class UserInfo(models.Model):
    """员工信息"""
    uid = models.AutoField(primary_key=True)            # 若无指定,Django为默认生成以id命名的一列自增数据
    username = models.CharField(max_length=32)          # 必须指定长度
    age = models.IntegerField(default=18)
    joindata = models.DateField()
    ug = models.ForeignKey("UserGroup",on_delete=models.CASCADE,)       # 生成数据库后,列名称变更为ug_‘外键表的id’
    hb = models.ManyToManyField("Hobby")

class HobbyList(models.Model):
    '''爱好清单'''
    hobby = models.CharField(max_length=16)
View Code

 

# 先查询或创建一个员工对象:
user_obj = models.UserInfo.objects.filter(username="张三1").first()
# 创建需要关联的爱好
hobby_obj1 = models.HobbyList.objects.filter(hobby="篮球").first()
hobby_obj2 = models.HobbyList.objects.filter(hobby="台球").first()
# 通过add绑定多对多关系
user_obj.hb.add(hobby_obj1,hobby_obj2)       #可同时添加一个或多个,知道id我们可以直接:user_obj.hb.add(1,2,3,)

# 如果我们需要将所有爱好与某一个员工进行关联,可以这样做:
user_obj = models.UserInfo.objects.filter(username="张三2").first()
hobby_objs = models.HobbyList.objects.all()
user_obj.hb.add(*hobby_objs)                    #知道id我们可以直接:user_obj.hb.add(*[1,2,3,])
# user_obj.hb.set(hobby_objs)			#同时添加多个对象,不需要加*

操作步骤:找到需要创建多关系的一个对象 –> 获取需要添加的多个对象 –> 用add方法绑定

删:remove   # 将部分特定的对象从被关联对象集合中去除

user_obj = models.UserInfo.objects.filter(username="张三2").first()    # 找到员工对象
hobby_obj = models.HobbyList.objects.filter(hobby="台球").first()      # 找到符合条件的作者对象
user_obj.hb.remove(hobby_obj)   # 也可以通过该方式解除多个对象:user_obj.hb.remove(hobby_obj1,hobby_obj3,…);知道id可以用其替换对象
# 如果需要解除多个对象的绑定[queryset/列表形式]
user_obj.hb.remove(*hobby_objs)      # 因为解除的是多条,得加个*;知道id可以用其替换对象

清除绑定:clear     # 清空被关联对象集合

user_obj = models.UserInfo.objects.filter(username="张三2").first()    # 找到员工对象
user_obj.hb.clear()
# 如果需要对多个对象进行清除绑定操作,我们可以使用for循环
for user_obj_item in user_obj:
	user_obj_item.hb.clear()

remove和clear的区别

  • remove:将需要清除的数据筛选出来,然后移除
  • clear:不用查,直接把数据都清空

重置

user_obj = models.UserInfo.objects.filter(username="张三2").first()
hobby_objs = models.HobbyList.objects.all()
user_obj.hb.set(hobby_objs)			#修改,之前的关系删除,注意:hobby_objs为可迭代对象;知道id可直接用其进行操作
# user_obj.hb.set([1,2,3])

# 正向查询:查询张三1的爱好清单
user_obj = models.UserInfo.objects.filter(username='张三1').first()
hobby_list = user_obj.hb.all().values_list("hobby")  #user_obj.hb.all()获取到的对象为与张三1相关的爱好对象
for hobby in hobby_list:
	print(hobby[0])

# 反向查询:查询爱好为篮球的有那几个员工
hobby_obj = models.HobbyList.objects.filter(hobby='篮球').first()
user_list = hobby_obj.userinfo_set.all()  #获取到的对象为爱好为篮球的员工对象
for user in user_list:
	print(user.username)

反向的增、删等操作与正向一致,不再敖述。

3.3 自定义第三张表的同时使用ManyToManyField

实例如下:

代码入下:

class UserInfo(models.Model):
    """员工信息"""
    uid = models.AutoField(primary_key=True)  # 若无指定,Django为默认生成以id命名的一列自增数据
    username = models.CharField(max_length=32)  # 必须指定长度
    age = models.IntegerField(default=18)
    joindata = models.DateField()
    ug = models.ForeignKey("UserGroup", on_delete=models.CASCADE, )  # 生成数据库后,列名称变更为ug_‘外键表的id’
    bh = models.ManyToManyField("HobbyList",through='U2H',through_fields=('user','hobby'))   # 只让其生成3张表,如果不加后面的参数产生生成4张表

class HobbyList(models.Model):
    '''爱好清单'''
    hobby = models.CharField(max_length=16)

class U2H(models.Model):
    user =  models.ForeignKey("UserInfo",on_delete=models.CASCADE,)
    hobby = models.ForeignKey("HobbyList",on_delete=models.CASCADE,)
    # 根据实际情况,我们可以加上联合唯一索引
    class Meta:
        unique_together = [
            ('user','hobby'),
        ]
View Code

这种杂交用法有如下特点:

1. ManyToManyField()字段创建第3张关系表,可以使用字段跨表查询,但无法直接操作第3张表

  • obj.m2m字段.all() 只有查询和清空方法(这点使我们可以简单的进行操作)

2. 自定义的第三张关系表操作与之前一致

3.4 使用choices表混合

举一个简单的例子,我们将男女放在同一张表中,然后建立第二张表让第一张表中的男女配对。

实例如下:

不加related_name报错图片:

原因分析:

按照常规的做法,我们可以将男女分开为两张表,然后用第三张表来存储男女之间的配对关系,添加FK即可,这样两个外键对应2张表,2个主键,可以识别男女。 但是现在两个外键对应1张表,反向查找,无法区分男女:

object对象女.Userinfo_set.all() object对象男.Userinfo_set().all

所以我们需要加入参数:related_name/related_query_name

related_name/related_query_name的使用          *****

这两个参数可以指定别名,主要方便反向查找:

  • related_name:obj.别名.all()
  • related_query_name:obj.别名_set.all()

注意:不知道是不是版本的问题,此例使用related_query_name也会报错,待续。。。

代码如下:

class UserInfos(models.Model):
    '''通过choices字段合并为一张表,第三张关系表使用models.ManyToManyField('Userinfos')生成'''
    nickname = models.CharField(max_length=32)
    username = models.CharField(max_length=32)
    sex = ((1, '男'), (2, '女'))
    gender = models.IntegerField(choices=sex)

class U2U(models.Model):
    b = models.ForeignKey(UserInfos,related_name='boys',on_delete=models.CASCADE,)
    g = models.ForeignKey(UserInfos,related_name='girls',on_delete=models.CASCADE,)
View Code

userinfos表增加数据:略

u2u表增加数据有以下两种方式:

# 方式一:直接通过id添加
models.U2U.objects.create(b_id=1,g_id=2)

# 方式二:通过对象添加
boy_obj = models.UserInfos.objects.filter(id=1).first()
girl_obj = models.UserInfos.objects.filter(id=3).first()
# models.U2U.objects.create(b_id=boy_obj.id, g_id=girl_obj.id)   #也是通过id添加,与方式一一致
models.U2U.objects.create(b=boy_obj, g=girl_obj)

# 查询与id为1的男性有关系的女性昵称
boy_obj = models.UserInfos.objects.filter(id=1).first()
girl_objs = boy_obj.boys.all()  # 反向查找,获取到U2U queryset对象,此时方式由boy_obj.u2u_set.all() -- > boy_obj.boys.all()
for girl_obj in girl_objs:
	print(girl_obj.g.nickname)      #正向查找获取昵称

3.5 多对多自关联

把两张表通过 choices字段合并为一张表,第三张关系表使用ManyToManyField()生成,相当于由原来的三张表变成了两张表。

实例如下:

表格样式:

代码如下:

class UserInfos(models.Model):
    '''通过choices字段合并为一张表,第三张关系表使用models.ManyToManyField('Userinfos')生成'''
    nickname = models.CharField(max_length=32)
    username = models.CharField(max_length=32)
    sex = ((1, '男'), (2, '女'))
    gender = models.IntegerField(choices=sex)
    m = models.ManyToManyField('UserInfos')
View Code

思考:怎么样通过代码的形式在第二张表中添加数据?

# 查询第一列数据:from_userinfos_id,默认第一列数据为男士
boy_obj = models.UserInfos.objects.filter(id=1).first()
girl_objs = boy_obj.u.all()             # 数据库查询代码:select xx from xx where from_userinfo_id = 1
for girl_obj in girl_objs:
	print(girl_obj.nickname)

# 查询第二列数据:from_userinfos_id,默认第二列数据为男士
girl_obj = models.UserInfos.objects.filter(id=2).first()
boy_objs = girl_obj.userinfos_set.all()     # 数据库查询代码:select xx from xx where to_userinfo_id = 2
for boy_obj in boy_objs:
	print(boy_obj.nickname)

注意点:需要我们自己指定第一列、第二列放什么参数,查询时也需要按照我们自己的指定进行操作。

四 性能相关

查询

# 1.普通查询
obj_list = models.U2H.objects.filter(user__username='张三1').all()
for obj in obj_list:             # for循环10次发送10次数据库查询请求
	print(obj.hobby.hobby)
# 特点:性能不好,进行多次数据库查询请求(不推荐使用)


# 2.使用select_related()
obj_list = models.U2H.objects.filter(user__username='张三1').select_related('hobby')
for obj in obj_list:
	print(obj.hobby.hobby)
# 特点:查询主动做连表,for循环时不用额外发请求,性能较高;适用于数据量少的情况

# 数据库操作代码:
# SELECT "app01_u2h"."id", "app01_u2h"."user_id", "app01_u2h"."hobby_id", "app01_hobbylist"."id", "app01_hobbylist"."hobby" 
# FROM "app01_u2h" INNER JOIN "app01_userinfo" ON ("app01_u2h"."user_id" = "app01_userinfo"."uid") 
# INNER JOIN "app01_hobbylist" ON ("app01_u2h"."hobby_id" = "app01_hobbylist"."id") 
# WHERE "app01_userinfo"."username" = '张三1'; 


# 3.使用prefetch_related()
obj_list = models.U2H.objects.filter(user__username='张三1').prefetch_related('hobby')
print(obj_list)
for obj in obj_list:
	print(obj.hobby.hobby)
# 特点:不做连表,做多次查询(去重);适用于数据量多、需要频繁查询的情况

# 数据库操作代码(有N个外键做1+N次单表查询):
# SELECT "app01_u2h"."id", "app01_u2h"."user_id", "app01_u2h"."hobby_id" 
# FROM "app01_u2h" INNER JOIN "app01_userinfo" 
# ON ("app01_u2h"."user_id" = "app01_userinfo"."uid") 
# WHERE "app01_userinfo"."username" = '张三1';
		
# SELECT "app01_hobbylist"."id", "app01_hobbylist"."hobby" 
# FROM "app01_hobbylist" 
# WHERE "app01_hobbylist"."id" IN (1, 3); 

更新:update()更新方式 优于 对象.save()更新方式

五 其他

1. choices详细

由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。

每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或ModelChoiceField中用作显示的内容。
在一个给定的model类的实例中,想得到某个choices字段的显示值,就调用get_FOO_display方法(这里的FOO就是choices字段的名称 )。例如:

class Flower(models.Model):
    colors = (
        ('1', '白色'),
        ('2', '红色'),
        ('3', '黄色'),
    )
    f_name = models.CharField(max_length=32)
    f_color = models.CharField(max_length=4, choices=colors)

操作

# 添加数据
models.Flower.objects.create(f_name='百合', f_color=1)
# 查询数据
obj = models.Flower.objects.filter(f_name='百合').first()
print(obj)
print(obj.f_color)
print(obj.get_f_color_display())    #注意加括号

2. null/blank的区别

  • null 如果为True,Django 将用NULL 来在数据库中存储空值, 默认值是 False
  • blank 如果为True,该字段允许不填,默认为False
  • null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。

六 总结

6.1 三种表现形式

  • models.xxx.objects.filter(*args, **kwargs) –> querySet [obj1,obj2]
  • models.xxx.objects.filter(*args, **kwargs).values(*args)  -->  querySet [{},{},{}]
  • models.xxx.objects.filter(*args, **kwargs).values_list(title) –> querySet [(),(),()]

6.2 查询方式

1对1查询

  • 正向查询:对象.1To1字段名.xxxx
  • 反向查询:对象.小写1To1表名.xxxx

1对多查询

正向:

  • 字段值:对象.外键字段.关联表字段
  • 对象:对象.M2M字段.all().values('xxx’)
  • values(外键字段__关联表字段)
  • values_list(外键字段__关联表字段)
  • filter(外键字段__关联表字段)

反向:

  • 字段值:对象.小写表名_set.关联表字段  /  字段值:对象.related_name属性值.关联表字段 
  • 对象:对象.小写表名_set.all().values('xxx’)  /  对象:对象.related_name属性值.all().values('xxx’) 
  • value、value_list:'小写表名' —> 获取id  /  'related_name属性值' —> 获取id
  • value、value_list:'小写表名__关联表字段' —> 获取相应字段数据  /  'related_name属性值__关联表字段' —> 获取相应字段数据
  • filter(小写表名__关联表字段)  /  filter(related_name属性值__关联表字段) 

多对多查询

  • 自定义第三张表:与1对多一致
  • M2M:正向操作: obj.m2m字段;反向操作: obj.小写表名_set(与外键跨表一致)
posted @ 2018-09-29 09:32  Joe1991  阅读(316)  评论(0)    收藏  举报