Djangoadvancedrouting

LearningDjango advancedroutingfunctions, including嵌套routing, 动态routing, routingin间件etc.

Djangoroutingsystemoverview

Django routingsystem用于将URLpathmap to 相应 视graphfunction or class视graph. It supports many 种routing模式, including静态routing, 动态routing, 嵌套routingetc..

Djangoroutingsystem corecomponent:

  • URLconf: URLconfigurationmodule, 定义URLpath and 视graph maprelationships.
  • Path converters: path转换器, 用于将URLpathin parameter转换 for Pythonobject.
  • View: 视graphfunction or class视graph, processingHTTPrequest并返回HTTPresponse.
  • Reverse URL resolution: 反向URL解析, through视graph名称生成URLpath.

提示

Django 2.0及以 on version推荐usingpath()function定义routing, 它providing了更简洁 语法 and 更 good readable 性. for 于需要正则表达式 complex routing, 可以usingre_path()function.

嵌套routing

嵌套routing is 指 in application URLconfigurationinpackage含otherapplication URLconfiguration, implementationrouting module化management.

basic嵌套routing

# mysite/urls.py
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    # package含blogapplication URLconfiguration
    path('blog/', include('blog.urls')),
    # package含shopapplication URLconfiguration
    path('shop/', include('shop.urls')),
]

然 after in application urls.pyin定义具体 routing:

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post//', views.post_detail, name='post_detail'),
    path('post/new/', views.post_create, name='post_create'),
    path('post//edit/', views.post_update, name='post_update'),
    path('post//delete/', views.post_delete, name='post_delete'),
]

嵌套routing and namespace

当 many 个applicationusing相同 视graph名称时, 可以usingnamespace来避免conflict:

# mysite/urls.py
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    #  for blogapplication添加namespace
    path('blog/', include(('blog.urls', 'blog'), namespace='blog')),
    #  for shopapplication添加namespace
    path('shop/', include(('shop.urls', 'shop'), namespace='shop')),
]

in 模板inusingnamespace:

<a href="{% url 'blog:post_list' %}">博客list</a>
<a href="{% url 'shop:product_list' %}">商品list</a>

动态routing

动态routing允许 in URLpathinpackage含parameter, 这些parameter会被传递给视graphfunction or class视graph.

path转换器

Djangoproviding了 many 种 in 置 path转换器:

  • str: 匹配除斜杠 out 任何非空string, 默认转换器.
  • int: 匹配正整数.
  • slug: 匹配由ASCII字母, number, 连字符 and under 划线组成 string.
  • uuid: 匹配UUID格式 string.
  • path: 匹配including斜杠 in in 任何非空string.

usingpath转换器

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # 匹配整数主键
    path('post//', views.post_detail, name='post_detail'),
    # 匹配slug
    path('post//', views.post_detail_by_slug, name='post_detail_by_slug'),
    # 匹配UUID
    path('article//', views.article_detail, name='article_detail'),
    # 匹配path
    path('files//', views.file_view, name='file_view'),
]

in 视graphfunctionin接收parameter:

# blog/views.py
def post_detail(request, pk):
    # pk is  from URLin提取 整数parameter
    post = get_object_or_404(BlogPost, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

def post_detail_by_slug(request, slug):
    # slug is  from URLin提取 slugparameter
    post = get_object_or_404(BlogPost, slug=slug)
    return render(request, 'blog/post_detail.html', {'post': post})

自定义path转换器

可以creation自定义path转换器, processing特殊 URLparameter格式.

步骤1: creation转换器class

# blog/converters.py
class YearConverter:
    regex = r'\d{4}'  # 匹配4位number
    
    def to_python(self, value):
        # 将URLpathin string转换 for Pythonobject
        return int(value)
    
    def to_url(self, value):
        # 将Pythonobject转换 for URLpathin string
        return str(value)

class MonthConverter(YearConverter):
    regex = r'\d{2}'  # 匹配2位number

步骤2: register转换器

# blog/urls.py
from django.urls import path, register_converter
from . import views
from .converters import YearConverter, MonthConverter

# register自定义转换器
register_converter(YearConverter, 'year')
register_converter(MonthConverter, 'month')

urlpatterns = [
    # using自定义转换器
    path('archive//', views.archive_by_year, name='archive_by_year'),
    path('archive///', views.archive_by_month, name='archive_by_month'),
]

in 视graphfunctioninusing自定义转换器:

# blog/views.py
def archive_by_year(request, year):
    # year is intclass型 parameter
    posts = BlogPost.objects.filter(pub_date__year=year)
    return render(request, 'blog/archive.html', {'posts': posts, 'year': year})

def archive_by_month(request, year, month):
    # year and month都 is intclass型 parameter
    posts = BlogPost.objects.filter(pub_date__year=year, pub_date__month=month)
    return render(request, 'blog/archive.html', {'posts': posts, 'year': year, 'month': month})

正则表达式routing

for 于 complex routing模式, 可以using正则表达式routing.

# blog/urls.py
from django.urls import path, re_path
from . import views

urlpatterns = [
    # using正则表达式匹配日期格式 YYYY/MM/DD
    re_path(r'^archive/(?P\d{4})/(?P\d{2})/(?P\d{2})/$', 
            views.archive_by_day, name='archive_by_day'),
    # using正则表达式匹配 many 个ID, 例such as /posts/1,2,3/
    re_path(r'^posts/(?P[\d,]+)/$', views.posts_by_ids, name='posts_by_ids'),
]

in 视graphfunctioninusing正则表达式parameter:

# blog/views.py
def archive_by_day(request, year, month, day):
    posts = BlogPost.objects.filter(
        pub_date__year=year,
        pub_date__month=month,
        pub_date__day=day
    )
    return render(request, 'blog/archive.html', {
        'posts': posts,
        'year': year,
        'month': month,
        'day': day
    })

def posts_by_ids(request, ids):
    # 将stringID转换 for 整数list
    id_list = [int(id) for id in ids.split(',')]
    posts = BlogPost.objects.filter(id__in=id_list)
    return render(request, 'blog/post_list.html', {'posts': posts})

routing视graph

Djangoproviding了 many 种 in 置 routing视graph, 可以直接用于processingcommon HTTPrequest.

重定向视graph

# blog/urls.py
from django.urls import path
from django.views.generic import RedirectView

urlpatterns = [
    # 永久重定向
    path('old-url/', RedirectView.as_view(url='/new-url/', permanent=True), name='old_url_redirect'),
    # using反向URL解析
    path('legacy-post//', RedirectView.as_view(pattern_name='post_detail', permanent=True)),
    # 动态重定向
    path('go-to-post//', RedirectView.as_view(url='/post/%(pk)s/'), name='go_to_post'),
]

模板视graph

# blog/urls.py
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    #  simple  模板视graph
    path('about/', TemplateView.as_view(template_name='blog/about.html'), name='about'),
    # 带额 out  on  under 文 模板视graph
    path('contact/', TemplateView.as_view(
        template_name='blog/contact.html',
        extra_context={'phone': '123-456-7890', 'email': 'contact@example.com'}
    ), name='contact'),
]

routingin间件

routingin间件 is 指 in processingrequest 过程in, in 视graph执行 before after 执行 code. 可以用于身份verification, log记录, cacheetc..

自定义in间件

# blog/middleware.py
class LoggingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # request to 达视graph之 before 执行
        print(f"Request: {request.method} {request.path}")
        
        # 调用视graph
        response = self.get_response(request)
        
        # 视graph返回response之 after 执行
        print(f"Response: {response.status_code}")
        
        return response
    
    # processing视graph抛出 exception
    def process_exception(self, request, exception):
        print(f"Exception: {exception}")
        return None

然 after in settings.pyinregisterin间件:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'blog.middleware.LoggingMiddleware',  # register自定义in间件
]

基于class in间件

可以using基于class in间件, implementation更 complex functions:

# blog/middleware.py
from django.utils.deprecation import MiddlewareMixin

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        # request to 达视graph之 before 执行
        if not request.user.is_authenticated and request.path not in ['/login/', '/register/']:
            return redirect('/login/')
    
    def process_response(self, request, response):
        # 视graph返回response之 after 执行
        return response

routingpermission控制

可以using装饰器 or in间件来控制routing 访问permission.

using装饰器控制permission

# blog/views.py
from django.contrib.auth.decorators import login_required, permission_required

@login_required
def profile(request):
    return render(request, 'blog/profile.html')

@permission_required('blog.change_blogpost')
def post_edit(request, pk):
    # 视graph逻辑...

usingin间件控制permission

# blog/middleware.py
class PermissionMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # 定义需要specificpermission URLpath
        permission_required = {
            '/admin/': 'is_staff',
            '/post/create/': 'blog.add_blogpost',
            '/post//edit/': 'blog.change_blogpost',
        }
        
        # checkuserpermission
        for path, permission in permission_required.items():
            if request.path.startswith(path):
                if permission.startswith('is_'):
                    # checkuserproperty
                    if not getattr(request.user, permission, False):
                        return HttpResponseForbidden()
                else:
                    # checkuserpermission
                    if not request.user.has_perm(permission):
                        return HttpResponseForbidden()
        
        response = self.get_response(request)
        return response

routingcache

可以usingDjango cachesystem来cacheroutingresponse, improvingperformance.

cache整个站点

# settings.py
MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',  # 必须放 in 第一个
    # otherin间件...
    'django.middleware.cache.FetchFromCacheMiddleware',  # 必须放 in 最 after 一个
]

# cacheconfiguration
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 600  # cache时间, 单位秒
CACHE_MIDDLEWARE_KEY_PREFIX = ''

cachespecific视graph

# blog/views.py
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # cache15分钟
def post_list(request):
    posts = BlogPost.objects.all()
    return render(request, 'blog/post_list.html', {'posts': posts})

cachespecificURL

# blog/urls.py
from django.urls import path
from django.views.decorators.cache import cache_page
from . import views

urlpatterns = [
    # cachepost_list视graph15分钟
    path('', cache_page(60 * 15)(views.post_list), name='post_list'),
    # otherURLconfiguration...
]

routingperformanceoptimization

routing performance for 整个application performance has important 影响. 以 under is 一些optimizationroutingperformance 建议:

  • 保持URLconf简洁: 避免 in URLconfin执行 complex 逻辑 or querydatalibrary.
  • using命名routing: using命名routing可以improvingcode readable 性 and 可maintenance性.
  • 避免using正则表达式routing: 正则表达式routing比普通routing slow , 尽量usingpath()function.
  • usinginclude()function: usinginclude()function可以将URLconf拆分 for many 个module, improvingperformance.
  • cacheroutingresponse: for 于频繁访问 视graph, 可以usingcache来improvingperformance.

练习 1: creation嵌套routing

  1. creation一个名 for "shop" Djangoapplication.
  2. in project urls.pyinpackage含shopapplication URLconfiguration, 并添加namespace.
  3. in shopapplication urls.pyin定义商品list, 商品详情, 商品creationetc.routing.
  4. creation相应 视graphfunction and 模板.
  5. testrouting is 否正常工作.

练习 2: creation自定义path转换器

  1. creation一个自定义path转换器, 用于匹配IPv4地址.
  2. register自定义转换器.
  3. using自定义转换器creationrouting, 用于显示specificIP地址 访问log.
  4. creation相应 视graphfunction and 模板.
  5. testrouting is 否正常工作.

练习 3: creationroutingin间件

  1. creation一个in间件, 用于记录所 has request URL, method and responsestatus码.
  2. in settings.pyinregisterin间件.
  3. testin间件 is 否正常工作.