美文网首页
【四】路由层URLconf

【四】路由层URLconf

作者: 一个无趣的人W | 来源:发表于2019-07-12 20:33 被阅读0次

    若能避开猛烈的狂喜,自然也不会有悲伤的来袭。

      URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应执行。

    一、Django 1.X版的url


    from django.conf.urls import url


    1、简单的路由配置

    (1)单一(基本)路由规则:
    url(r'^login/$', views.index),
    
    (2)基于正则表达式的路由

      格式:url('正则表达式','视图函数内存地址')

    • 单个正则表达式
      单个括号需要在视图函数detail中增加一个参数来接收正则中匹配的内容。
    # 路由
    url(r'^detail-(\d+).html/$', views.detail)
    # 视图函数
    def detail(request,nid):#增加nid形参,接受url传递的参数
        pass
    
    • 多个正则表达式(按顺序接收)
    # 路由
    url(r'^detail-(\d+)-(\d+).html/$', views.detail)
    # 视图函数
    # 接受参数时候,按顺序接受,比如url是http://127.0.0.1:8000/detail-2-3.html/,第一个参数为2,第二个参数为3
    def detail(request,nid,sid):
        pass
    

    🐷 Tips:
      若要从URL中捕获一个值,只需要用一对圆括号将它包住;
      不需要添加一个前导的反斜杠,因为每个URL都有。例如,应该是^login而不是^/login
      每个正则表达式前面的r是可选的但是建议加上,他告诉Python这个字符串是“原始的”——字符串中任何字符都不应该转义;
      路由匹配从上到下依次匹配,一旦匹配成功就不再匹配后面的路由,所以在写路由时要注意顺序。

    2、无名分组和有名分组

    • 无名分组
      将括号内正则表达式匹配到的内容当作位置参数自动传递给对应视图函数。
    # 路由
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
    # 视图函数
    def article_detail(request,'2017','01','07'):
        pass
    
    • 有名分组
      在Python正则表达式中,命名正则表达式组的语法是
      (?P<name>pattern),其中name 是组的名称,pattern 是要匹配的模式。
      将括号内正则表达式匹配到的内容当作关键字参数自动传递给对应视图函数,函数中使用名字作为行参获取值。
    # 路由
    url(r'^detail-(?P<id1>\d+)-(?P<id2>\d+).html/$', views.detail),
    # 视图函数
    def detail(request,id1,id2):
        pass
    

    🐷 Tips:
      位置参数必须要在关键字参数前面;
      无名分组和有名分组不支持混合使用,虽然不能混合使用,但可以重复使用相同类型的

    • 给路由添加额外参数
      除了正则路由可以给视图函数传递参数以外,还可以使用路由中直接添加参数
    # 路由
    url(r'^detail/', views.detail, {"nid": 12}),
    # 视图函数,增加nid参数来接收传递的参数
    def detail(request,nid):
        pass
    

    3、反向解析

      在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
      意思就是根据名字动态获取该名字所对应的能够匹配上url的一个结果:url(r'^index666888/',views.index,name='index'),这就是无论前面的路径怎么变,都能根据index这个名字匹配到相应的视图
      在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:

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

    🐷Tips:
      当命名你的URL 模式时,请确保使用的名称不会与其它应用中名称冲突。如果你的URL 模式叫做comment,而另外一个应用中也有一个同样的名称,当你在模板中使用这个名称的时候不能保证将插入哪个URL。在URL 名称中加上一个前缀,比如应用的名称,将减少冲突的可能。我们建议使用myapp-comment 而不是comment。

    (1) 不传参(name只能匹配固定的url)

    ------- 路由 -------
     url(r'^index/$', views.index,name='my_index'),
    
    ------- 前端模板中 -------
    <a href="{% url 'my_index' %}">Index</a>
    
    ------- 后端python中 -------
    from django.core.urlresolvers import reverse
    from django.http import HttpResponseRedirect
    def redirect_to_year(request):
        # ...
        year = 2006
        # ...
        return HttpResponseRedirect(reverse('my_index'))  # 同redirect("/my_index/")
    

    (2) 传参(无名分组的反向解析和有名分组的反向解析)

    • 无名分组的反向解析
    ------- 路由 -------
    url(r'^index/(\d+)/',views.index,name='list')  # index/1/ 或者 index/13213/ 或者 index/12/
    
    ------- 前端模板中 -------
    <a href="{% url 'index' user_obj.pk %}">my_index</a>
    
    ------- 后端python中 -------
    from django.core.urlresolvers import reverse
    from django.http import HttpResponseRedirect
    
    def redirect_to_year(request):
        # ...
        user_obj.pk=1
        # ...
        return HttpResponseRedirect(reverse('index',args=(user_obj.pk,)))   # 同redirect("/index/1/")
    
    • 有名分组的反向解析
    ------- 路由 -------
    url(r'^index/(?P<xxx>\d+)/',views.index,name='edit')
    
    ------- 前端模板中 -------
    <a href="{% url 'edit' user_obj.pk %}">Edit</a>
    或 <a href="{% url 'edit' xxx=user_obj.pk %}">Edit</a>
    
    ------- 后端python中 -------
    from django.core.urlresolvers import reverse
    from django.http import HttpResponseRedirect
    
    def redirect_to_year(request):
        # ...
        user_obj.pk=1
        # ...
        return HttpResponseRedirect(reverse('edit',args=(user_obj.pk,)))   # 同redirect("/edit/1/")
     或 return HttpResponseRedirect(reverse('edit',kwargs={'xxx':user_obj.pk}))   # 同redirect("/edit/1/")
    

    (3) Model中使用获取URL——自定义get_absolute_url ()方法

    class NewType(models.Model):
        caption = models.CharField(max_length=16)
    
        def get_absolute_url(self):
            """
            为每个对象生成一个URL
            应用:在对象列表中生成查看详细的URL,使用此方法即可!!!
            :return:
            """
            # return '/%s/%s' % (self._meta.db_table, self.id)
            # 或
            from django.urls import reverse
            return reverse('NewType.Detail', kwargs={'nid': self.id})
    

    详解:在视图函数中反向获取url的方式是重载get_absolute_url方法。
       接下来以一个电子购物网站为例来理解:
    模型表(models):

    from django.db import models
    from django.urls import reverse
    
    class Category(models.Model):
        name = models.CharField(max_length=150, db_index=True)
        slug = models.SlugField(max_length=150, unique=True ,db_index=True)  # 🌈字段详解见下文
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
    
        class Meta:
            ordering = ('name', )
            verbose_name = 'category'
            verbose_name_plural = 'categories'
    
        def __str__(self):
            return self.name
    
        def get_absolute_url(self):  # 🌸重点!!
            return reverse('shop:product_list_by_category', args=[self.slug])  # 这里的self.slug相当于当前对象category.slug
    
    class Product(models.Model):
        category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
        name = models.CharField(max_length=100, db_index=True)
        slug = models.SlugField(max_length=100, db_index=True) 
        description = models.TextField(blank=True)
        price = models.DecimalField(max_digits=10, decimal_places=2)
        available = models.BooleanField(default=True)
        stock = models.PositiveIntegerField()  # 🌈字段详解见下文
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)
        image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    
        class Meta:
            ordering = ('name', )
            index_together = (('id', 'slug'),)
    
        def __str__(self):
            return self.name
    
        def get_absolute_url(self):  # 🌸重点!!
            return reverse('shop:product_detail', args=[self.id, self.slug])  # 这里的self.id相当于当前对象product.id,self.slug相当于当前对象product.slug
    
    ————————————————————————————
    总结:
    在category.get_obsolute_url里面用的是category.slug来连接到该product的list.html页面;
    在product.get_obsolute_url里面用的是product.id和product.slug来连接到该product的detail.html页面;
    至于这里为什么对象名都会变成小写,这就要进入源码中看,源码中是这么写的:
        get_absolute_url_override = settings.ABSOLUTE_URL_OVERRIDES.get(opts.label_lower)  # 🐷这里的label_lower就是将表名小写,进入label_lower,返回的是return '%s.%s' % (self.app_label, self.model_name)
        if get_absolute_url_override:
            setattr(cls, 'get_absolute_url', get_absolute_url_override)
    ————————————————————————————
    

    视图(view):

    from django.shortcuts import render, get_object_or_404 
    from .models import Category, Product
    
    def product_list(request, category_slug=None):
        category = None
        '''
        下面的categories是查询出的分类对象,
        print(categories.get_absolute_url())——>打印出/categories/-/
        '''
        categories = Category.objects.all()
        '''
        下面的products是查询出的产品对象,
        print(products.get_absolute_url())——>打印出/product/01-phone/
        '''
        products = Product.objects.filter(available=True)
        if category_slug:
            category = get_object_or_404(Category, slug=category_slug)  # 🐷详解见下文
            products = Product.objects.filter(category=category)
    
        context = {
            'category': category,
            'categories': categories,
            'products': products
        }
        return render(request, 'shop/product/list.html', context)
    
    def product_detail(request, id, slug):
        product = get_object_or_404(Product, id=id, slug=slug, available=True)   # 🐷详解见下文
        context = {
            'product': product
        }
        return render(request, 'shop/product/detail.html', context)
    
    ————————————————————————————
    总结:
    在视图中查询出的对象如果调用get_absolute_url()方法,会得到相应表名的一个伪url,
    后面就可以拼接到路径中,比如得到http://127.0.0.1:8000/shop/product/01-phone/
    ————————————————————————————
    

    路由(url):

    from django.conf.urls import url
    from app import views
    
    urlpatterns = [
            url("^$", views.product_list, name="product_list"),
            url(r"^(?P<category_slug>[\w-]+)/$", views.product_list,name="product_list_by_category"),
            url(r"^(?P<id>\d+)/(?P<slug>[\w-]+)/$", views.product_detail,name="product_detail"),
            ]
    
    ————————————————————————————
    总结:
    < >里面只是在传递关键字参数时用到,在路由中不会体现,只会得到后面的值或者名字,
    比如第二个路由可能是http://127.0.0.1:8000/phone-name,第三个路由可能是http://127.0.0.1:8000/01/sony-phone
    ————————————————————————————
    

    前端页面(list.html):

    {% for product in products %}
           <div class="col-md-4">
             <div class="thumbnail">
                 <a href="{{ product.get_absolute_url }}">  # 🐷重点!!!
                     <img src="{% if product.image %} {{ product.image.url }} {% else %} {% static 'img/default.jpg' %} {% endif %}" alt="..." style="height: 130px; width: auto">
                 </a>
                 <div class="caption">
                     <h3 class="text-center">
                         <a href="{{ product.get_absolute_url }}">{{ product.name }}</a>
                     </h3>
                     <p class="text-center">Kshs. {{ product.price }}</p>
                 </div>
             </div>
            </div>
     {% endfor %}
    
    ————————————————————————————
    总结:
    这里的 product.get_absolute_url就会得到一个完整路由地址,完成跳转
    ————————————————————————————
    

    🍃 补充知识点:

    • 模型表中的SlugField字段
    slug = models.SlugField(max_length=150, unique=True ,db_index=True)
    

      SlugField 本质上相当于存放字符串,但是在意义上,主要用于把某些字段形成语义化的,可以访问的短网址(slug)字符串。
      举例而言,假设有一个关于文章的 models,有两个字段,分别是标题 title 和短网址 slug。

    class Article(models.Model):
        title = models.CharField(max_length=100)
        slug = models.SlugField(max_length=40)
    

      这时候存入 models 的一条信息,title 是 The song of ice and fire。如果我们想用文章的标题作为 url 进行访问,完整的标题可能是:www.xxx.com/article/The song of ice and fire但是 url 是不能出现空格的,因此空格会被转变成 %20,最后网址得到www.xxx.com/article/The%20song%20of%20ice%20and%20fire,我们不希望出现这么多难看的 %20,而是希望用短横线 - 替代空格,得到www.xxx.com/article/the-song-of-ice-and-fire,而 Django 的 django.utils.text提供了一个方法叫 slugify,可以把刚才的文章标题 The song of ice and fire 做两个转变:全部转化成小写字母;空格部分替换成短横线 -,因为这种转变主要用于做 url 拼接,所以我们把这种结果都统一放在 SlugField 字段里面,用以构建语义化的 url 网址。

    pre_save + slugify 构建 SlugField
    (1)slug 是唯一的字段,不能出现重复 slug,所以 SlugField 的属性是 unique=True。
    (2)使用 pre_save.connect(pre_save_post_receiver, sender=Post) 把 Post 这个 models 以及自定义的处理函数pre_save_post_receiver 连接起来,表示在 Post 存储之前,先用该函数进行数据预处理。
    (3)预处理主要针对 slug,如果没有 slug,就把标题 title 用 slugify 转化成 slug。
    (4)如果数据库中已经有了相同的 slug,就把之前的 slug 对应的数据的 id 拿过来,拼接在本次 slug 的后面。当然也可以用其他方法进行区分,避免重复。

    在models.py中

    from django.db import models
    from django.db.models.signals import pre_save
    from django.utils.text import slugify
    
    class Post(models.Model):
        # ...
        title = models.CharField(max_length=150)
        slug = models.SlugField(unique=True)
    
    def create_slug(instance):
        slug = slugify(instance.title)
        qs = Post.objects.filter(slug=slug).order_by("-id")
        exists = qs.exists()
        if exists:
            new_slug = "{0}-{1}".format(slug, qs.first().id)
            return new_slug
        return slug
    
    def pre_save_post_receiver(sender, instance, *args, **kwargs):
        if not instance.slug:
            instance.slug = create_slug(instance)
    
    pre_save.connect(pre_save_post_receiver, sender=Post)
    
    • PositiveIntegerField()字段:主要存储正整数数据
    ---------------------- 一些常用的字段 ----------------------
    ----------------------------------------------------------
    1、AutoField:自增Field域,自动增加的一个数据库字段类型,例如id字段就可以使用该数据类型
    2、BigAutoField:和AutoField相同,只是比AutoField要大
    3、BigIntegerField:大整型,只要用于存储整型的数据
    4、BinaryField:主要是存储原始的二进制数据
    5、BooleanField:主要是存储布尔类型的数据,0和1
    6、CharField:主要存储字符串的数据类型
    7、DateField:主要存储日期类型的数据类型
    8、DateTimeField:主要存储时间相关的数据类型
    9、DecimalField:主要存储固定精度的十进制数据
    10、EmailField:存储电子邮件格式的数据
    11、FileField:存储文件类型的数据
    12、FilePathField:存储文件路径的数据
    13、FloatField:存储浮点型数据
    14、ImageField:存储图片型数据
    15、IntegerField:存储整型数据
    16、GenericIPAddressField:存储IP地址信息数据
    17、NullBooleanField:可以存储布尔值数据,也可以存储空null数据
    18、PositiveIntegerField:主要存储正整数数据
    19、SmallIntegerField:小整型,主要用于存储整型的数据
    20、TextField:存储文章内容信息数据,存储比较长的文本信息
    21、TimeField:存储时间信息
    22、URLField:存储URL网址信息
    
    • get_object_or_404
      用特定查询条件获取某个对象,成功则返回该对象,否则引发一个 Http404。
      get_object_or_404(klass, *args, **kwargs)
      参数:
      klass:接受一个 Model 类,Manager 或 QuerySet 实例,表示你要对该对象进行查询。
      **kwargs:查询条件,格式需要被 get() 和 filter() 接受。
      举个例子🌰:
    from django.shortcuts import render
    from django.shortcuts import get_object_or_404
    from myApp.models import Book 
    
    def my_view(request):
        context = {}
        # 查询主键为1的书,找不到返回http404
        books = get_object_or_404(Book, pk=1)
        context['books'] = books
        return render(request, 'my_view.html', context)
    

    除了传递一个 Model,还可以传递一个 QuerySet 实例:

    queryset = Book.objects.filter(title__startswith='红')
    books = get_object_or_404(queryset, pk=1)
    

    以上写法等价于:
    get_object_or_404(Book, title__startswith='红', pk=1)

    4、路由分发

      当项目中的app有多个时候,导入include路由分发,那么项目总路由不再做路由与视图函数的匹配工作,而是做一个中转。
    🐷Tips:
      django里面每一个app都可以有自己的urls.py,static静态文件夹,templates模板文件夹;
      路由分发的时候千万不要加$

    • 配置工程中的总路由(urls.py)
    #工程的urls.py配置
    
    #导入include
    from django.conf.urls import url,include
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^blog/',include('blog.urls'))  # 匹配到以blog开头的都转发到blog应用目录下的urls.py中
    ]
    
    • 在blog应用下新建urls.py,配置目的url
    from django.conf.urls import url,include
    from django.contrib import admin
    #导入blog下的view函数
    from . import views
    urlpatterns = [
        url(r'^index/',views.index),
        url(r'^detail/',views.detail),
    ]
    

    5、名称空间

      命名空间(英语:Namespace)是表示标识符的可见范围。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。
      由于name没有作用域,Django在反解URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回。我们在开发项目时,会经常使用name属性反解出URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入了命名空间
      使用namespacse参数给include命名,可以后续反向解析url。

    • 工程下的urls.py
    from django.contrib import admin
    from cmdb import views
    from django.conf.urls import url,include
    urlpatterns = [
        url(r'^app01/',include('app01.urls',namespace='myapp01')),
        url(r'^app02/',include('app01.urls',namespace='myapp02')),
    ]
    
    • app01下面的urls.py
    from django.conf.urls import url
    from . import views
    urlpatterns = [
        url(r'^index/',views.index,name='myindex'),
    ]
    
    • app02下面的urls.py
    from django.conf.urls import url
    from . import views
    urlpatterns = [
        url(r'^index/',views.index,name='myindex'),  # 这时候与app01中的name相同
    ]
    
    • 当反向解析时
    # views.py中进行反向解析
    def index(request):
        v=reverse('myapp01:myindex')
        print(v)      #输出/app01/index/
        print(request.resolver_match)
        return HttpResponse('ok')
    
    # 在模版中解析改地址
    {% url 'myapp01:myindex' %}
    
    # 带参数的url反解析
    v = reverse('myapp01:myindex', kwargs={'page':11})
    {% url 'myapp01 :myindex' page=12 %}
    

    6、伪静态网页(以html结尾)

      伪静态就是通过某些技术(rewrite)把动态网页的url地址伪装成静态url。此技术并未提升性能,甚至会使得性能下降。唯一的好处就是让搜索引擎收录网站内容,提升网页被搜索的概率。
      搜索引擎优化(seo):

    # 在路由后面加了.html,假装它是一个静态的网页
    url(r'^index.html',views.index)
    url(r'^index/\d+.html',views.index)
    

    二、Django2.0版的path

    传送门(2.0的URL dispatcher):
    https://docs.djangoproject.com/en/2.0/topics/http/urls/#example


    from django.urls import path,re_path


    1、在1.X中使用的是url方式,而2.0中推荐使用path模块,而且如果使用正则路由,则要引入re_path,并且一定要使用()把正则表达式包起来,然后用?P正式表达式这种形式来表式。
    2、在2.0中使用namespace会报错,说什么app_name不存在什么的,这时需要在urls.py中加上app_name='test',加在urlpatterns=[…]上面即可。

    • 思考情况如下:
    urlpatterns = [  
        re_path('articles/(?P<year>[0-9]{4})/', year_archive),  
        re_path('article/(?P<article_id>[a-zA-Z0-9]+)/detail/', detail_view),  
        re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/edit/', edit_view),  
        re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/delete/', delete_view),  
    ]
    

    考虑下这样的两个问题:
      第一个问题,函数 year_archive 中year参数是字符串类型的,因此需要先转化为整数类型的变量值,当然year=int(year) 不会有诸如如TypeError或者ValueError的异常。那么有没有一种方法,在url中,使得这一转化步骤可以由Django自动完成?
      第二个问题,三个路由中article_id都是同样的正则表达式,但是你需要写三遍,当之后article_id规则改变后,需要同时修改三处代码,那么有没有一种方法,只需修改一处即可?
      在Django2.0中,可以使用 path 解决以上的两个问题。

    • Path converters(转换器)
      Django默认支持以下5个转化器:
        str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
        int,匹配正整数,包含0。
        slug,匹配字母、数字以及横杠、下划线组成的字符串。
        uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
        path,匹配任何非空字符串,包含了路径分隔符

    • 基本示例

    from django.urls import path  
    from . import views  
    urlpatterns = [  
        path('articles/2003/', views.special_case_2003),  
        path('articles/<int:year>/', views.year_archive),  
        path('articles/<int:year>/<int:month>/', views.month_archive),  
        path('articles/<int:year>/<int:month>/<slug>/', views.article_detail),  
    ]  
    

    基本规则:
      使用尖括号(<>)从url中捕获值。
      捕获值中可以包含一个转化器类型(converter type),比如使用 <int:name> 捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符。
      无需添加前导斜杠。


    Django2.0视图调用形式
    • 注册自定义转换器
      对于一些复杂或者复用的需要,可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:
        regex 类属性,字符串类型。
        to_python(self, value) 方法,value是由类属性 regex 所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。
        to_url(self, value) 方法,和 to_python 相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。
      例子🌰:
    class FourDigitYearConverter:  
        regex = '[0-9]{4}'  
        def to_python(self, value):  
            return int(value)  
        def to_url(self, value):  
            return '%04d' % value  
    

    使用register_converter 将其注册到URL配置中:

    from django.urls import register_converter, path  
    from . import converters, views  
    register_converter(converters.FourDigitYearConverter, 'yyyy')  
    urlpatterns = [  
        path('articles/2003/', views.special_case_2003),  
        path('articles/<yyyy:year>/', views.year_archive),  
        ...  
    ]  
    

    相关文章

      网友评论

          本文标题:【四】路由层URLconf

          本文链接:https://www.haomeiwen.com/subject/qsejkctx.html