• Django的路由层,视图层和模版层


    一.Django的路由层

    简单的路由(url)配置

    from django.conf.urls import url
    
    from . import views
    
    urlpatterns = [
        url(r'^articles/2003/$', views.special_case_2003),
        url(r'^articles/([0-9]{4})/$', views.year_archive),
        url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
        url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
    ]

    注意:

    • 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号。
    • 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles
    • 每个正则表达式前面的'r' 是可选的但是建议加上。它告诉Python 这个字符串是“原始的” —— 字符串中任何字符都不应该转义

    1.有名分组

      给分组起一个名字,实现关键字传参

      在Python 正则表达式中,命名正则表达式组的语法是(?P<name>pattern),其中name 是组的名称,pattern 是要匹配的模式。

    from django.conf.urls import url
    
    from . import views
    
    urlpatterns = [
        url(r'^articles/2003/$', views.special_case_2003),
        url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
        url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
    ]

      捕获的值作为关键字参数而不是位置参数传递给视图函数

    '''
        /articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数,而不是views.month_archive(request, '2005', '03')。
        /articles/2003/03/03/ 请求将调用函数views.article_detail(request, year='2003', month='03', day='03')。
    
        '''

    2.分发

      把每一个app自己的url分发各自的路由文件中

    '''
    At any point, your urlpatterns can “include” other URLconf modules. This
    essentially “roots” a set of URLs below other ones.
    
    Including another URLconf
        1. Add an import:  from blog import urls as blog_urls
        2. Add a URL to urlpatterns:  url(r'^blog/', include(blog.urls))
    
    '''
    
    from django.conf.urls import include, url
    
    urlpatterns = [
       url(r'^admin/', admin.site.urls),
       url(r'^blog/', include('blog.urls')),
    ]

    3.反向解析

      不要硬编码url,通过别名解析url

    在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:

    • 在模板中:使用url 模板标签。
    • 在Python 代码中:使用django.core.urlresolvers.reverse() 函数。

    urls.py

    from django.conf.urls import url
    
    from . import views
    
    urlpatterns = [
        #...
        url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
        #...
    ]

    在模板中

    <a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
    
    <ul>
    {% for yearvar in year_list %}
    <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
    {% endfor %}
    </ul>

    在python中

    from django.core.urlresolvers import reverse
    from django.http import HttpResponseRedirect
    
    def redirect_to_year(request):
        # ...
        year = 2006
        # ...
        return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))   # 同redirect("/path/")

    4.名称空间

      命名空间(英语:Namespace)是表示标识符的可见范围。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。

      由于name没有作用域,Django在反解URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回
      我们在开发项目时,会经常使用name属性反解出URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入了命名空间。

    project的urls.py:

    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^app01/', include("app01.urls",namespace="app01")),
        url(r'^app02/', include("app02.urls",namespace="app02")),
    ]

    app01.urls:

    urlpatterns = [
        url(r'^index/', index,name="index"),
    ]

    app02.urls:

    urlpatterns = [
        url(r'^index/', index,name="index"),
    ]

    app01.views 

    from django.core.urlresolvers import reverse
    
    def index(request):
    
        return  HttpResponse(reverse("app01:index"))

    app02.views

    from django.core.urlresolvers import reverse
    
    def index(request):
    
        return  HttpResponse(reverse("app02:index"))

    二.Django的视图层

    1.视图函数

      一个视图函数,简称视图,是一个简单的Python 函数,它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. . . 是任何东西都可以。

      约定是将视图放置在项目或应用程序目录中的名为views.py的文件中。

    返回当前日期和时间作为HTML文档的视图:

    from django.shortcuts import render, HttpResponse, HttpResponseRedirect, redirect
    import datetime
    
    def current_datetime(request):
        now = datetime.datetime.now()
        html = "<html><body>It is now %s.</body></html>" % now
        return HttpResponse(html)

      首先,我们从 django.shortcuts模块导入了HttpResponse类,以及Python的datetime库。

      接着,我们定义了current_datetime函数。它就是视图函数。每个视图函数都使用HttpRequest对象作为第一个参数,并且通常称之为request

      这个视图会返回一个HttpResponse对象,其中包含生成的响应。每个视图函数都负责返回一个HttpResponse对象。

    请求对象(request)和响应对象(HttpResponse)

    2.HttpRequest对象

      request属性

      django将请求报文中的请求行、首部信息、内容主体封装成 HttpRequest 类中的属性

    1.HttpRequest.GET
    
      一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。
    
    2.HttpRequest.POST
    
      一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。
    
      POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。
       因此,不应该使用 if request.POST  来检查使用的是否是POST 方法;应该使用 if request.method == "POST"
      另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。
       
       注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用:
            request.POST.getlist("hobby")
    
    3.HttpRequest.body
    
      一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。
      但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。
    
    
    4.HttpRequest.path
    
      一个字符串,表示请求的路径组件(不含域名)。
      例如:"/music/bands/the_beatles/"
    
    5.HttpRequest.method
    
      一个字符串,表示请求使用的HTTP 方法。必须使用大写。
      例如:"GET""POST"
    
    
    6.HttpRequest.encoding
    
      一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。
       这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。
       接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。
       如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。
    
    
    7.HttpRequest.META
    
       一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:
    
        CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
        CONTENT_TYPE —— 请求的正文的MIME 类型。
        HTTP_ACCEPT —— 响应可接收的Content-Type。
        HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
        HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
        HTTP_HOST —— 客服端发送的HTTP Host 头部。
        HTTP_REFERER —— Referring 页面。
        HTTP_USER_AGENT —— 客户端的user-agent 字符串。
        QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
        REMOTE_ADDR —— 客户端的IP 地址。
        REMOTE_HOST —— 客户端的主机名。
        REMOTE_USER —— 服务器认证后的用户。
        REQUEST_METHOD —— 一个字符串,例如"GET""POST"。
        SERVER_NAME —— 服务器的主机名。
        SERVER_PORT —— 服务器的端口(是一个字符串)。
       从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,
        都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_  前缀。
        所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
    
    8.HttpRequest.FILES
    
      一个类似于字典的对象,包含所有的上传文件信息。
       FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。
      注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会
       包含数据。否则,FILES 将为一个空的类似于字典的对象。
    
    
    9.HttpRequest.COOKIES
    
      一个标准的Python 字典,包含所有的cookie。键和值都为字符串。
    
    
    
    10.HttpRequest.session
    
       一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。
        完整的细节参见会话的文档。
    
    
    11.HttpRequest.user(用户认证组件下使用)
    
      一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
    
      如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们。
    
        例如:
    
        if request.user.is_authenticated():
            # Do something for logged-in users.
        else:
            # Do something for anonymous users.
    
    
           user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。
    
         -------------------------------------------------------------------------------------
    
        匿名用户
        class models.AnonymousUser
    
        django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:
    
        id 永远为None。
        username 永远为空字符串。
        get_username() 永远返回空字符串。
        is_staff 和 is_superuser 永远为False。
        is_active 永远为 False。
        groups 和 user_permissions 永远为空。
        is_anonymous() 返回True 而不是False。
        is_authenticated() 返回False 而不是True。
        set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
        New in Django 1.8:
        新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。

    最常见的几种

    print(request.method) # 请求方式
    print(request.path)   # 请求路径
    print(request.POST)   # POST的请求数据  字典格式
    print(request.GET)   # GET的请求数据  字典格式
    print(request.META)  # 请求头

    request常用方法

    1.HttpRequest.get_full_path()
    
      返回 path,如果可以将加上查询字符串。
    
      例如:"/music/bands/the_beatles/?print=true"
    
    
    2.HttpRequest.is_ajax()
    
      如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。
    
      大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。
    
      如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware,
       你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。

    HttpResponse()对象

    响应对象主要有三种形式:

      HttpResponse()

      render()   

      redirect()

    HttpResponse()括号内直接跟一个具体的字符串作为响应体

    render( "页面"

     render(request, template_name[, context])  结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。

    参数:

      request: 用于生成响应的请求对象。

      template_name:要使用的模板的完整名称,可选的参数

      context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。

    render方法就是将一个模板页面中的模板语法进行渲染,最终渲染成一个html页面作为响应体。

    redirect()     重定向

      传递要重定向的一个硬编码的URL

    def my_view(request):
        ...
        return redirect('/some/url/')

    key:两次请求

    1)301和302的区别。
    
      301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取
      (用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。
    
      他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;
    
      302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。 SEO302好于301
    
     
    
    2)重定向原因:
    (1)网站调整(如改变网页目录结构);
    (2)网页被移到一个新地址;
    (3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
            这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的
        网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。

    三.Django的模版层

    python的模版: HTML代码+模版语法

    def current_time(req):
        # ================================原始的视图函数
        # import datetime
        # now=datetime.datetime.now()
        # html="<html><body>现在时刻:<h1>%s.</h1></body></html>" %now
    
    
        # ================================django模板修改的视图函数
        # from django.template import Template,Context
        # now=datetime.datetime.now()
        # t=Template('<html><body>现在时刻是:<h1>{{current_date}}</h1></body></html>')
        # #t=get_template('current_datetime.html')
        # c=Context({'current_date':str(now)})
        # html=t.render(c)
        #
        # return HttpResponse(html)
        #另一种写法(推荐)
        import datetime
        now=datetime.datetime.now()
        return render(req, 'current_datetime.html', {'current_date':str(now)[:19]})

    1.模板语法之变量

    在 Django 模板中遍历复杂数据结构的关键是句点字符, 语法:  

    1
    {{var_name}}

    views.py

    def index(request):
        import datetime
        s="hello"
        l=[111,222,333]    # 列表
        dic={"name":"yuan","age":18}  # 字典
        date = datetime.date(1993, 5, 2)   # 日期对象
     
        class Person(object):
            def __init__(self,name):
                self.name=name
        person_yuan=Person("yuan")  # 自定义类对象
        person_egon=Person("egon")
        person_alex=Person("alex")
        person_list=[person_yuan,person_egon,person_alex]
        return render(request,"index.html",{"l":l,"dic":dic,"date":date,"person_list":person_list})

    template:

    <h4>{{s}}</h4>
    <h4>列表:{{ l.0 }}</h4>
    <h4>列表:{{ l.2 }}</h4>
    <h4>字典:{{ dic.name }}</h4>
    <h4>日期:{{ date.year }}</h4>
    <h4>类对象列表:{{ person_list.0.name }}</h4>

    注意:句点符也可以用来引用对象的方法(无参数方法)

    <h4>字典:{{ dic.name.upper }}</h4>

    2.模版之过滤器

    语法: 

    {{obj|filter__name:param}}

      default    如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值。

    {{ value|default:"nothing" }}

      length     返回值的长度。它对字符串和列表都起作用。

    {{ value|length }}
    如果 value 是 ['a', 'b', 'c', 'd'],那么输出是 4。

      filesizeformat    将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB''4.1 MB''102 bytes', 等等)

    {{ value|filesizeformat }}
    如果 value 是 123456789,输出将会是 117.7 MB。  

      date   如果 value=datetime.datetime.now()

    {{ value|date:"Y-m-d" }}  

      slice      如果 value="hello world"

    {{ value|slice:"2:-1" }}

      truncatechars      如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。

    {{ value|truncatechars:9 }}
    参数:要截断的字符数

      safe     通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。

    value="<a href="">点击</a>"
    
    {{ value|safe}}

    3.模版之标签

      for标签

    遍历每一个元素:

    {% for person in person_list %}
        <p>{{ person.name }}</p>
    {% endfor %}

    可以利用{% for obj in list reversed %}反向完成循环。

    遍历一个字典:

    {% for key,val in dic.items %}
        <p>{{ key }}:{{ val }}</p>
    {% endfor %}

    注:循环序号可以通过{{forloop}}显示  

    forloop.counter            The current iteration of the loop (1-indexed)
    forloop.counter0           The current iteration of the loop (0-indexed)
    forloop.revcounter         The number of iterations from the end of the loop (1-indexed)
    forloop.revcounter0        The number of iterations from the end of the loop (0-indexed)
    forloop.first              True if this is the first time through the loop
    forloop.last               True if this is the last time through the loop

    for ... empty

    for 标签带有一个可选的{% empty %} 从句,以便在给出的组是空的或者没有被找到时,可以有所操作。

    {% for person in person_list %}
        <p>{{ person.name }}</p>
    
    {% empty %}
        <p>sorry,no person here</p>
    {% endfor %}

    if 标签

    {% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。

    {% if num > 100 or num < 0 %}
        <p>无效</p>
    {% elif num > 80 and num < 100 %}
        <p>优秀</p>
    {% else %}
        <p>凑活吧</p>
    {% endif %}

    with

    使用一个简单地名字缓存一个复杂的变量,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的

    例如:

    {% with total=business.employees.count %}
        {{ total }} employee{{ total|pluralize }}
    {% endwith %}

    csrf_token

    这个标签用于跨站请求伪造保护 ,放在from表单中

    4.自定义标签和过滤器

      1、在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.

      2、在app中创建templatetags模块(模块名只能是templatetags)

      3、创建任意 .py 文件,如:my_tags.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from django import template
    from django.utils.safestring import mark_safe
     
    register = template.Library()   #register的名字是固定的,不可改变
     
     
    @register.filter
    def filter_multi(v1,v2):
        return  v1 * v2
    <br>
    @register.simple_tag
    def simple_tag_multi(v1,v2):
        return  v1 * v2
    <br>
    @register.simple_tag
    def my_input(id,arg):
        result = "<input type='text' id='%s' class='%s' />" %(id,arg,)
        return mark_safe(result)

      4、在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py

    1
    {% load my_tags %} 

      5、使用simple_tag和filter(如何调用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -------------------------------.html
    {% load xxx %}  
          
    # num=12
    {{ num|filter_multi:2 }} #24
     
    {{ num|filter_multi:"[22,333,4444]" }}
     
    {% simple_tag_multi 2 5 %}  参数不限,但不能放在if for语句中
    {% simple_tag_multi num 5 %}

    注意:filter可以用在if等语句后,simple_tag不可以

    1
    2
    3
    {% if num|filter_multi:30 100 %}
        {{ num|filter_multi:30 }}
    {% endif %}

    5.模板继承(extend)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <link rel="stylesheet" href="style.css" />
        <title>{% block title %}My amazing site{%/span> endblock %}</title>
    </head>
    
    <body>
        <div id="sidebar">
            {% block sidebar %}
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog/">Blog</a></li>
            </ul>
            {% endblock %}
        </div>
    
        <div id="content">
            {% block content %}{% endblock %}
        </div>
    </body>
    </html>

      这个模版,我们把它叫作 base.html, 它定义了一个可以用于两列排版页面的简单HTML骨架。“子模版”的工作是用它们的内容填充空的blocks。

      在这个例子中, block 标签定义了三个可以被子模版内容填充的block。 block 告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置。

    {% extends "base.html" %}
     
    {% block title %}My amazing blog{% endblock %}
     
    {% block content %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% endfor %}
    {% endblock %}

      extends 标签是这里的关键。它告诉模版引擎,这个模版“继承”了另一个模版。当模版系统处理这个模版时,首先,它将定位父模版——在此例中,就是“base.html”。

      那时,模版引擎将注意到 base.html 中的三个 block 标签,并用子模版中的内容来替换这些block。根据 blog_entries 的值

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <link rel="stylesheet" href="style.css" />
        <title>My amazing blog</title>
    </head>
     
    <body>
        <div id="sidebar">
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/blog/">Blog</a></li>
            </ul>
        </div>
     
        <div id="content">
            <h2>Entry one</h2>
            <p>This is my first entry.</p>
     
            <h2>Entry two</h2>
            <p>This is my second entry.</p>
        </div>
    </body>
    </html>

      子模版并没有定义 sidebar block,所以系统使用了父模版中的值。父模版的 {% block %} 标签中的内容总是被用作备选内容(fallback)。

    注意:

      1.如果你在模版中使用 {% extends %} 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作。

      2.在base模版中设置越多的 {% block %} 标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。

      3.如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% block %} 中。

      4.If you need to get the content of the block from the parent template, the {{ block.super }} variable will do the trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it. Data inserted using {{ block.super }} will not be automatically escaped (see the next section), since it was already escaped, if necessary, in the parent template.

      5.为了更好的可读性,你也可以给你的 {% endblock %} 标签一个 名字 。

    {% block content %}
    ...
    {% endblock content %}  

        在大型模版中,这个方法帮你清楚的看到哪一个  {% block %} 标签被关闭了。

      6.不能在一个模版中定义多个相同名字的 block 标签

  • 相关阅读:
    thinkPHP中怎么访问域名直接跳到后台登录页面
    使用ajax方法实现form表单的提交
    微信小程序根据生日获取年龄
    微信小程序出现 text 设置行高 line-height 无效的问题
    微信小程序中换行,空格(多个空格)写法
    如何用css实现弧度圆角?三角形以及圆形
    微信小程序的bindtap事件
    JS控制只能输入数字并且最多允许小数点两位
    微信小程序如何做金额输入限制
    微信小程序使用函数的三种方法
  • 原文地址:https://www.cnblogs.com/chenxi67/p/9832680.html
Copyright © 2020-2023  润新知