返回顶部
扩大
缩小

Gaidy's

11.Django2.0文档

第四章 模板

 1.标签

(1)if/else

{% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系统会
显示在 {% if %} 和 {% endif %} 之间的任何内容,例如:

{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% endif %}

{% else %} 标签是可选的:

{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}

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

(2)for

{% 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>

给标签增加一个 reversed 使得该列表被反向迭代:

{% for athlete in athlete_list reversed %}
...
{% endfor %}

可以嵌套使用 {% for %} 标签:

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

Django不支持退出循环操作。 如果我们想退出循环,可以改变正在迭代的变量,让其仅仅包含需要迭代的项目。 同理,Django也不支持continue语句,我们无法让当前迭代操作跳回到循环头部。

在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性。

forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。

例子:

def lists(request):
    lists = [1,2,3,4,5]
    return render(request,"lists.html",{'lists':lists})
 {% for item in lists %}
        <p>{{ forloop.counter }}:{{ item }}</p>
    {% endfor %}

结果:

1:1

2:2

3:3

4:4

5:5

-->>forloop.counter0 类似于 forloop.counter ,但是它是从0计数的。 第一次执行循环时这个变量会被设置为0。

-->>forloop.revcounter0 类似于 forloop.revcounter ,但它以0做为结束索引。 在第一次执行循环时,该变量会被置为序列的项的个数减1

forloop.first 是一个布尔值,如果该迭代是第一次执行,那么它被置为````

 {% for item in lists %}
        {% if forloop.first %}
            <li style="color: red">{{ forloop.counter }}:{{ item }}</li>
        {% else %}
            <li>{{ forloop.counter }}:{{ item }}</li>
        {% endif %}

    {% endfor %}

结果:

forloop.last 是一个布尔值;在最后一次执行循环时被置为True。

forloop.parentloop 是一个指向当前循环的上一级循环的 forloop 对象的引用(在嵌套循环的情况下)。

2.注释

就像HTML或者Python,Django模板语言同样提供代码注释。 注释使用 {# #} :

{# This is a comment #}

如果要实现多行注释,可以使用`` {% comment %}`` 模板标签,就像这样:

{% comment %}
This is a
multi‐line comment.
{% endcomment %}

3.过滤器

模板过滤器是在变量被显示前修改它的值的一个简单方法。 过滤器使用管道字符,

{{ name|lower }}    #它功能是转换文本为小写。

过滤管道可以被* 套接* ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入,如此下去

{{ my_list|first|upper }}   #查找列表的第一个元素并将其转化为大写。

4.locals() 技巧

如果你是个喜欢偷懒的程序员并想让代码看起来更加简明,可以利用 Python 的内建函数 locals() 。它返回的字典对所有局部变量的名称与值进行映射。 因此,前面的视图可以重写成下面这个样子:

def lists(request):
    lists = [1,2,3,4,5]
    return render(request,"lists.html",locals())

locals() 的值,它囊括了函数执行到该时间点时所定义的一切变量

5.模板继承

 (1)定义基础模板base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    {% block content %}{% endblock %}
    {% block footer %}
        <hr>
        <p>Thanks for visiting my site</p>
    {% endblock %}
</body>
</html>

这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档,我们将在所有页面中使用。 子模板的作用就是重载、添加或保留那些块的内容。
所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个 {% block %} 标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。

(2)子模板current_time.html

{% extends 'base.html' %}

{% block title %}The current time{% endblock%}

{% block content %}
    <p>It is now {{ current_date }}</p>
{% endblock %}

(3)views.py

from django.shortcuts import HttpResponse,render

import datetime


def current_datetime(request):
    current_date = datetime.datetime.now()
    return render(request,'cuttent_datetime.html',{'current_date':current_date})

       以下是其工作方式:在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签,模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由 {% block title %} 替换,同样地,网页的内容一块将由 {% block content %} 替换。注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。

如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作
用。
一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因
此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话
说,钩子越多越好。
如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。
如果你需要访问父模板中的块的内容,使用 {{ block.super }} 这个标签吧,这一个魔法变量将会表现出
父模板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双
向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在 父 模板中这个坑所填充的内容。如果模
板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
{% extends %} 对所传入模板名称使用的加载方法和 get_template() 相同。 也就是说,会将模板名称被添
加到 TEMPLATE_DIRS 设置之后。
多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也
可以是个变量。 这使得你能够实现一些很酷的动态功能。
模板继承的一些诀窍

 第五章 模型

 1.MTV and MVC

把数据存取逻辑、业务逻辑和表现逻辑组合在一起的概念有时被称为软件架构的Model-View-Controller(MVC)模式。 在这个模式中, Model 代表数据存取层,View 代表的是系统中选择显示什么和怎么显示的部分,Controller 指的是系统中根据用户输入并视需要访问模型,以决定使用哪个视图的那部分。

     M:数据存取部分,由django数据库层处理,本章要讲述的内容。

  V:选择显示哪些数据要显示以及怎样显示的部分,由视图和模板处理。
  C:根据用户输入委派视图的部分,由 Django 框架根据 URLconf 设置,对给定 URL 调用适当的Python 函数

 由于 C 由框架自行处理,而 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),Django 也被称为MTV 框架。在 MTV 开发模式中:

   M:代表模型(Model),即数据存取层。 该层处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等

   T:代表模板(Template),即表现层。 该层处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。

  V:代表视图(View),即业务逻辑层。 该层包含存取模型及调取恰当模板的相关逻辑。 你可以把它看作模型与模板之间的桥梁。

 2.设置数据库为Mysql

 在settings里面修改配置

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django',        #数据库名字
        'USER': 'root',          #账号
        'PASSWORD': '123456',      #密码
        'HOST': '127.0.0.1',    #IP
        'PORT': '3306',                   #端口
    }
}

在app的init.py里面导入pymysql模块

import pymysql
pymysql.install_as_MySQLdb()

设置好后打开 python manage.py shell 来进行测试。输入一下命令,如果没有报错说明,说明数据库配置是正确的

>>> from django.db import connection
>>> cursor = connection.cursor()

3.第一个模型

书籍/作者/出版商 数据库

   一个作者有姓,有名及email地址。

  出版商有名称,地址,所在城市、省,国家,网站。
  书籍有书名和出版日期。 它有一个或多个作者(和作者是多对多的关联关系[many-to-many]), 只有一个出版商(和出版商是一对多的关联关系[one-to-many],也被称作外          键[foreign key])
from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

class Book(models.Model):
    title = models.CharField(max_length=100)
    publication_date = models.DateField()
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher,on_delete = models.CASCADE)

打开shell,添加publisher

当我们打印整个publisher列表时,我们没有得到想要的有用信息,只需要为Publisher对象添加一个__str__方法 ,就可以对Publisher对象更容易理解

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

    def __str__(self):
        return self.name

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

    def __str__(self):
        return '%s%s'%(self.first_name,self.last_name)

class Book(models.Model):
    title = models.CharField(max_length=100)
    publication_date = models.DateField()
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher,on_delete = models.CASCADE)

    def __str__(self):
        return self.title
添加__str__方法

4.数据过滤

 可以使用`` filter()`` 方法对数据进行过滤:

也可以传入多个参数

Publisher.objects.filter(country="U.S.A.", state_province="CA")

魔术般的操作

 Publisher.objects.filter(name__contains="Apr")

其他的一些查找类型有:
  icontains(大小写无关的LIKE),startswith和endswith, 还有range

5.获取单个对象

上面的例子中`` filter()`` 函数返回一个记录集,这个记录集是一个列表。 相对列表来说,有些时候我们更需要获取单个的对象, `` get()`` 方法就是在此时使用的:

 

这样,就返回了单个对象,而不是列表(更准确的说,QuerySet)。 所以,如果结果是多个对象,会导致抛出异常:

 

如果查询没有返回结果也会抛出异常:

 6.数据排序

在运行前面的例子中,你可能已经注意到返回的结果是无序的。 我们还没有告诉数据库 怎样对结果进行排序,所以我们返回的结果是无序的。
在你的 Django 应用中,你或许希望根据某字段的值对检索结果排序,比如说,按字母顺序。 那么,使用order_by()这个方法就可以搞定了。

我们可以对任意字段进行排序,如果需要以多个字段为标准进行排序(第二个字段会在第一个字段的值相同的情况下被使用到),使用多个参数就可以了,如下:

我们还可以指定逆向排序,在前面加一个减号‐前缀:

限制返回的数据

7.更新和删除对象

update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录。 以下示例演示如何将所有Publisher的country字段值由’U.S.A’更改为’USA’:

 

update()方法会返回一个整型数值,表示受影响的记录条数。 在上面的例子中,这个值是2

 删除数据库中的对象只需调用该对象的delete()方法即可:

 

第六章 Django站点管理

 1.创建admin用户

python manage.py createsuperuser

 访问:http://127.0.0.1:8000/admin/,输入用户名,密码登录进入管理界面

可以看到只有Groutps和Users

要向让app book里面的models显示在这里面,只需把book注册到admin

book app下的admin.py

from django.contrib import admin
from book import models

# Register your models here.
admin.site.register(models.Author)
admin.site.register(models.Book)
admin.site.register(models.Publisher)

然后就可以在管理界面看到

中文显示的方法:Meta

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

    class Meta:
        verbose_name_plural = "出版社"

    def __str__(self):
        return self.name

2.Admin工作原理

当服务启动时,Django从`` url.py`` 引导URLconf,然后执行`` admin.autodiscover()`` 语句。 这个函数遍历INSTALLED_APPS配置,并且寻找相关的admin.py文件。 如果在指定的app目录下找到admin.py,它就执行其中的代码。
在`` book`` 应用程序目录下的`` admin.py`` 文件中,每次调用`` admin.site.register()`` 都将那个模块注册到管理工具中。 管理工具只为那些明确注册了的模块显示一个编辑/修改的界面。应用程序`` django.contrib.auth`` 包含自身的`` admin.py`` ,所以Users和Groups能在管理工具中自动显示。其它的django.contrib应用程序,如django.contrib.redirects,其它从网上下在的第三方Django应用程序一样,都会自行添加到管理工具。

 3.设置字段可选

 你或许会发现管理工具有个限制:编辑表单需要你填写每一个字段,然而在有些情况下,你想要某些字段是可选的。 举个例子,我们想要Author模块中的email字段成为可选,即允许不填。 在现实世界中,你可能没有为每个作者登记邮箱地址。

为了指定email字段为可选,你只要编辑Book模块
class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(null=True,blank=True)

这些代码告诉Django,作者的邮箱地址允许输入一个空值

 4.自定义字段标签    

 在编辑页面中,每个字段的标签都是从模块的字段名称生成的。规则很简单: 用空格替换下划线;首字母大写

然而,字段名称并不总是贴切的。有些情况下,你可能想自定义一个标签。 你只需在模块中指定verbose_name。

 举个例子,说明如何将Author.email的标签改为e-mail,中间有个横线。

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(null=True,blank=True,verbose_name='e-mail')

改之前

改之后

5.自定义ModelAdmi类

 迄今为止,我们做的blank=True、null=True和verbose_name修改其实是模块级别,而不是管理级别的。 也就是说,这些修改实质上是构成模块的一部分,并且正好被管理工具使用,而不是专门针对管理工具的。

除了这些,Django还提供了大量选项让你针对特别的模块自定义管理工具。 这些选项都在ModelAdminclasses里面,这些类包含了管理工具中针对特别模块的配置。

 (1)自定义列表

 默认情况下,Author下面只显示每个作者的姓名

我们可以在这基础上改进,添加其它字段,从而改变列表的显示。比如说:在这个列表中可以看到作者的邮箱地址,为了达到这个目的,我们将为Author模块定义一个ModelAdmin类。 这个类是自定义管理工具的关键,其中最基本的一件事情是允许你指定列表中的字段。

 修改admin.py

from django.contrib import admin
from book import models

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name','last_name','email')

admin.site.register(models.Author,AuthorAdmin)
admin.site.register(models.Book)
admin.site.register(models.Publisher)

可以看到如下效果

解释一下代码:

我们新建了一个类AuthorAdmin,它是从django.contrib.admin.ModelAdmin派生出来的子类,保存着一个类的自定义配置,以供管理工具使用。我们只自定义了一项:list_display,它是一个字段名称的元组,用于列表显示。当然,这些字段名称必须是模块中有的。

我们修改了admin.site.register()调用,在Author后面添加了AuthorAdmin。你可以这样理解:用AuthorAdmin选项注册Author模块。

(2)添加快速查询栏

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name','last_name','email')
    search_fields = ('first_name','last_name')

在页面顶端看到一个查询栏

(3)添加过滤器

接下来,让我们为Book列表页添加一些过滤器。
class BookAdmin(admin.ModelAdmin):
    list_display = ('title','publisher','publication_date')
    list_filter = ('publication_date',)

admin.site.register(models.Author,AuthorAdmin)
admin.site.register(models.Book,BookAdmin)
admin.site.register(models.Publisher)

可以看到右边有filter,Django为日期型字段提供了快捷过滤方式,它包含:今天、过往七天、当月和今年

另外一种过滤日期的方式是使用date_hierarchy选项,如:
class BookAdmin(admin.ModelAdmin):
    list_display = ('title','publisher','publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
修改好后,页面中的列表顶端会有一个逐层深入的导航条,效果如下. 它从可用的年份开始,然后逐层细分到月乃至日。
请注意,date_hierarchy接受的是* 字符串* ,而不是元组。因为只能对一个日期型字段进行层次划分。
 (4)降序排列
让我们改变默认的排序方式,按publication date降序排列。
class BookAdmin(admin.ModelAdmin):
    list_display = ('title','publisher','publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)

 (5)自定义编辑表单

 正如自定义列表那样,编辑表单多方面也能自定义。
首先,我们先自定义字段顺序。 默认地,表单中的字段顺序是与模块中定义是一致的。 我们可以通过使用ModelAdmin子类中的fields选项来改变它:
class BookAdmin(admin.ModelAdmin):
    list_display = ('title','publisher','publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    fields = ('title', 'authors', 'publisher', 'publication_date')

改之前:

改之后:

通过fields这个选项,你可以排除一些不想被其他人编辑的fields 只要不选上不想被编辑的field(s)即可。

例如,在book数据库中,我们可以隐藏publication_date,以防止它被编辑。
fields = ('title', 'authors', 'publisher')
 

 

另一个常用的编辑页面自定义是针对多对多字段的。 真如我们在book编辑页面看到的那样,`` 多对多字段`` 被展现成多选框。虽然多选框在逻辑上是最适合的HTML控件,但它却不那么好用。 如果你想选择多项,你必须还要按下Ctrl键。 虽然管理工具因此添加了注释(help_text),但是当它有几百个选项时,它依然显得笨拙。
更好的办法是使用
filter_horizontal。让我们把它添加到BookAdmin中,然后看看它的效果。
class BookAdmin(admin.ModelAdmin):
    list_display = ('title','publisher','publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    filter_horizontal = ('authors',)
请注意移除fields选项,以使得编辑页面包含所有字段。
加之前

加之后,明显方便多了

针对那些拥有十个以上选项的`` 多对多字段`` 使用filter_horizontal。 这比多选框好用多了。
 
ModelAdmin类还支持filter_vertical选项。 它像filter_horizontal那样工作,除了控件都是垂直排列,而不是水平排列的。 至于使用哪个,只是个人喜好问题。
 filter_vertical = ('authors',)

filter_horizontal和filter_vertical选项只能用在多对多字段上, 而不能用于ForeignKey字段。 默认地,管理工具使用`` 下拉框`` 来展现`` 外键`` 字段。但是,正如`` 多对多字段`` 那样,有时候你不想忍受因装载并显示这些选项而产生的大量开销。 例如,我们的book数据库膨胀到拥有数千条publishers的记录,以致于book的添加页面装载时间较久,因为它必须把每一个publisher都装载并显示在`` 下拉框`` 中。
解决这个问题的办法是使用`` raw_id_fields`` 选项。它是一个包含外键字段名称的元组,它包含的字段将被展现成`` 文本框`` ,而不再是`` 下拉框`` 。

 

class BookAdmin(admin.ModelAdmin):
    list_display = ('title','publisher','publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    filter_vertical = ('authors',)
    raw_id_fields = ('publisher',)

效果:

在这个输入框中,你输入什么呢? publisher的数据库ID号。 考虑到人们通常不会记住这些数据库ID,管理工具提供了一个放大镜图标方便你输入。点击那个图标将会弹出一个窗口,在那里你可以选择想要添加的publisher。

 第七章 用户、用户组和权限

因为你是用超级用户登录的,你可以创建,编辑和删除任何对像。 然而,不同的环境要求有不同的权限,系统不允许所有人都是超级用户。 管理工具有一个用户权限系统,通过它你可以根据用户的需要来指定他们的权限,从而达到部分访问系统的目的。
你通过管理界面编辑用户及其许可就像你编辑别的对象一样。 浏览用户和用户组区域的时候已经见过这些了。 如你所想,用户对象有标准的用户名、密码、邮箱地址和真实姓名,同时它还有关于使用管理界面的权限定义。
首先,这有一组三个布尔型标记:
  活动标志:它用来控制用户是否已经激活。 如果一个用户帐号的这个标记是关闭状态,而用户又尝试用它
       登录时,即使密码正确,他也无法登录系统。
  成员标志:它用来控制这个用户是否可以登录管理界面(即:这个用户是不是你们组织里的成员) 由于用
          户系统可以被用于控制公众页面(即:非管理页面)的访问权限(详见第十四章),这个标志可用来区分
       公众用户和管理用户。
  超级用户标志:它赋予用户在管理界面中添加、修改和删除任何项目的权限。 如果一个用户帐号有这个标
         志,那么所有权限设置(即使没有)都会被忽略。
 
普通的活跃,非超级用户的管理用户可以根据一套设定好的许可进入。 管理界面中每种可编辑的对象(如:books、authors、publishers)都有三种权限:创建许可,编辑许可和删除许可。 给一个用户授权许可也就表明该用户可以进行许可描述的操作。
当你创建一个用户时,它没有任何权限,该有什么权限是由你决定的。 例如,你可以给一个用户添加和修改publishers的权限,而不给他删除的权限。 请注意,这些权限是定义在模块级别上,而不是对象级别上的。据个例子,你可以让小强修改任何图书,但是不能让他仅修改由机械工业出版社出版的图书。 后面这种基于对象级别的权限设置比较复杂,并且超出了本书的覆盖范围。

 注释:

权限管理系统也控制编辑用户和权限。 如果你给某人编辑用户的权限,他可以编辑自己的权限,这种能力可能不是你希望的。 赋予一个用户修改用户的权限,本质上说就是把他变成一个超级用户。你也可以给组中分配用户。 一个组简化了给组中所有成员应用一套许可的动作。 组在给大量用户特定权限的时候很有用。
 
 
 

posted on 2019-12-24 19:32  Gaidy  阅读(381)  评论(0编辑  收藏  举报

导航