Django 之 高级视图和URL配置

随着 Django 应用变得复杂,它的 URLconf 也在增长,并且维护这些导入可能使得管理变麻烦


你可以尝试以下方法


1.通过导入 views 模块本身来避免这个麻烦


# from contact.view import contact,thanks,contanct_mod
from contact import view as v


2.Django 还提供了另一种方法可以在 URLconf 中为某个特别的模式指定视图函数: 你可以传入一个包含模块名和函数名的字符串,而不是函数对象本身


url(r'^/contactthanks/$','contact.view.contanct_mod'),



字符串方法的好处如下:

  • 更紧凑,因为不需要你导入视图函数。

  • 如果你的视图函数存在于几个不同的 Python 模块的话,它可以使得 URLconf 更易读和管理。

函数对象方法的好处如下:

  • 更容易对视图函数进行包装(wrap)。 参见本章后面的《包装视图函数》一节。

  • 更 Pythonic,就是说,更符合 Python 的传统,如把函数当成对象传递。

两个方法都是有效的,甚至你可以在同一个 URLconf 中混用它们。 决定权在你。

使用多个视图前缀

在实践中,如果你使用字符串技术,特别是当你的 URLconf 中没有一个公共前缀时,你最终可能混合视图。 然而,你仍然可以利用视图前缀的简便方式来减少重复。 只要增加多个 patterns()  对象,象这样:



from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.views',
    (r'^hello/$', 'hello'),
    (r'^time/$', 'current_datetime'),
    (r'^time/plus/(\d{1,2})/$', 'hours_ahead'),
)

urlpatterns += patterns('weblog.views',
    (r'^tag/(\w+)/$', 'tag'),
)
要特别说明一下,patterns()返回的对象是可相加的




调试模式中的特例

动态构建 urlpatterns,你可能想利用这一技术,在 Django 的调试模式下修改 URLconf 的行为。 为了做到这一点,只要在运行时检查 DEBUG 配置项的值即可


from django.conf import settings
from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^$', views.homepage),
    (r'^(\d{4})/([a-z]{3})/$', views.archive_month),
)

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^debuginfo/$', views.debug),
    )
URL链接/debuginfo/  只在你的 DEBUG  配置项设为 True  时才有效



使用命名组


目前为止的所有 URLconf 例子中,我们使用简单的无命名 正则表达式组。即,在我们想要捕获的URL部分上加上小括号,Django 会将捕获的文本作为位置参数传递给视图函数。 在更高级的用法中,还可以使用 命名 正则表达式组来捕获URL,并且将其作为 关键字 参数传给视图。

估计上面的有点抽象,有的人未必理解。下面从代码开始看:

关键字参数 对比 位置参数

def sell(item, price, quantity):
    print "Selling %s unit(s) of %s at %s" % (quantity, item, price)
sell('Socks', '$2.50', 6)  #这行==于下面的任何一行
 
sell(item='Socks', price='$2.50', quantity=6) 
sell(item='Socks', quantity=6, price='$2.50')
sell(price='$2.50', item='Socks', quantity=6)
sell(price='$2.50', quantity=6, item='Socks')
sell(quantity=6, item='Socks', price='$2.50')
sell(quantity=6, price='$2.50', item='Socks')


通过对比也就说明了, 使用关键字参数来调用它 ,前提是你知名了 关键字参数 和值


可以混合关键字和位置参数,只要所有的位置参数列在关键字参数之前。


sell('Socks', '$2.50', quantity=6)
sell('Socks', price='$2.50', quantity=6)
sell('Socks', quantity=6, price='$2.50')

在 Python 正则表达式中,命名的正则表达式组的语法是 (?P<name>pattern) ,这里 name 是组的名字,而pattern 是匹配的某个模式。

下面是一个使用无名组的 URLconf 的例子:

from django.conf.urls.defaults import *
from mysite import views
#在django的url格式中,()中的内容是作为参数传递给view的 urlpatterns = patterns('',
    (r'^articles/(\d{4})/$', views.year_archive),
    (r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
)

下面是相同的 URLconf,使用命名组进行了重写:

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^articles/(?P<year>\d{4})/$', views.year_archive),
    (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
)

例如,如果不带命名组,请求 /articles/2006/03/ 将会等同于这样的函数调用:

month_archive(request, '2006', '03')

而带命名组,同样的请求就会变成这样的函数调用:

month_archive(request, year='2006', month='03')

使用命名组的优缺点:

优点:

    使用命名组可以让你的URLconfs更加清晰,减少搞混参数次序的潜在BUG,还可以让你在函数定义中对参数重新排序。 接着上面这个例子,如果我们想修改URL把月份放到 年份的 前面 ,而不使用命名组的话,我们就不得不去修改视图 month_archive 的参数次序。 如果我们使用命名组的话,修改URL里提取参数的次序对视图没有影响。

缺点:

    命名组的代价就是失去了简洁性: 一些开发者觉得命名组的语法丑陋和显得冗余。 命名组的另一个好处就是可读性强。

理解匹配/分组算法

需要注意的就是:

命名组和非命名组是不能同时存在于同一个URLconf的模式中的。 如果你这样做,Django不会抛出任何错误,但你可能会发现你的URL并没有像你预想的那样匹配正确。

以下是URLconf解释器有关正则表达式中命名组和 非命名组所遵循的算法:

  • 如果有任何命名的组,Django会忽略非命名组而直接使用命名组。

  • 否则,Django会把所有非命名组以位置参数的形式传递。

  • 在以上的两种情况,Django同时会以关键字参数的方式传递一些额外参数


传递额外的参数到视图函数中

有时你会发现你写的视图函数是十分类似的,只有一点点的不同


# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^foo/$', views.foo_view),
    (r'^bar/$', views.bar_view),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foo_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template1.html', {'m_list': m_list})

def bar_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template2.html', {'m_list': m_list})
开始优化:在视图中检查并决定使用哪个模板来去除代码的冗余,就像这样:



# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^(foo)/$', views.foobar_view),
    (r'^(bar)/$', views.foobar_view),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foobar_view(request, url):
    m_list = MyModel.objects.filter(is_new=True)
    if url == 'foo':
        template_name = 'template1.html'
    elif url == 'bar':
        template_name = 'template2.html'
    return render_to_response(template_name, {'m_list': m_list})
有一个明显的缺点,就是URL 的耦合,假如你 把 /foo/  改成 /fooey/ 的话,那么你就得记住要去改变视图里面的代码 ,python 注重的就是代码的简洁,显然,这样还不够。


你可以尝试这样:


URL配置参数的优雅解决方法: URLconf里面的每一个模式都可以包含第三个数据: 一个关键字参数的字典:

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}),
    (r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foobar_view(request, template_name):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response(template_name, {'m_list': m_list})

如你所见,这个例子中,URLconf指定了 template_name 。 而视图函数会把它当成另一个参数。

伪造捕捉到的URLconf值
如说你有匹配某个模式的一堆视图,以及一个并不匹配这个模式但视图逻辑是一样的URL。 这种情况下,你可以通过向同一个视图传递额外URLconf参数来伪造URL值的捕捉

例如:

            

显示某一个特定日子的某些数据的应用,URL类似这样的:

/mydata/jan/01/
/mydata/jan/02/
/mydata/jan/03/
# ...
/mydata/dec/30/
/mydata/dec/31/

这太简单了,你可以在一个URLconf中捕捉这些值,像这样(使用命名组的方法):

urlpatterns = patterns('',
    (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)
视图逻辑:

    

def my_view(request, month, day):
    # ....

 当你想添加另外一个使用 my_view 视图但不包含month和/或者day的URL时,问题就出现了。

比如你可能会想增加这样一个URL, /mydata/birthday/ 

这时,

你可以这样利用额外URLconf参数:

urlpatterns = patterns('',
    (r'^mydata/birthday/$', views.my_view, {'month': 'jan', 'day': '06'}),
    (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)

在这里最帅的地方莫过于你根本不用改变你的视图函数。 视图函数只会关心它 获得 了 参数,它不会去管这些参数到底是捕捉回来的还是被额外提供的。month和day

创建一个通用视图
抽取出我们代码中共性的东西是一个很好的编程习惯,把共用的抽取出来,这是很好的习惯
def say_hello(person_name):
    print 'Hello, %s' % person_name

def say_goodbye(person_name):
    print 'Goodbye, %s' % person_name

我们可以把问候语提取出来变成一个参数:

def greet(person_name, greeting):
    print '%s, %s' % (greeting, person_name)

通过使用额外的URLconf参数,你可以把同样的思想应用到Django的视图中

urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^events/$', views.event_list),
    (r'^blog/entries/$', views.entry_list),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import Event, BlogEntry

def event_list(request):
    obj_list = Event.objects.all()
    return render_to_response('mysite/event_list.html', {'event_list': obj_list})

def entry_list(request):
    obj_list = BlogEntry.objects.all()
    return render_to_response('mysite/blogentry_list.html', {'entry_list': obj_list})
进行抽象出来:

    

# urls.py

from django.conf.urls.defaults import *
from mysite import models, views

urlpatterns = patterns('',
    (r'^events/$', views.object_list, {'model': models.Event}),
    (r'^blog/entries/$', views.object_list, {'model': models.BlogEntry}),
)

# views.py

from django.shortcuts import render_to_response

def object_list(request, model):
    obj_list = model.objects.all()
    template_name = 'mysite/%s_list.html' % model.__name__.lower()
    return render_to_response(template_name, {'object_list': obj_list})
就这样小小的改动,我们突然发现我们有了一个可复用的,模型无关的视图! 从现在开始,当我们需要一个视图来显示一系列的对象时,我们可以简简单单的重用这一个 object_list  视图,而无须另外写视图代码了


当然,你还可以自己决定你需要传递的模版值,例如:

#字典中加一个关于模板的键/值
(r'^events/$','mydjango.views.object_list', {'model': models.Book,'template_name':'Book.html'}),
提供视图配置选项

一个应用中比较常见的可供配置代码是模板名字:

def my_view(request, template_name):
    var = do_something()
    return render_to_response(template_name, {'var': var})
了解捕捉值和额外参数之间的优先级 额外的选项
当冲突出现的时候,额外URLconf参数优先于捕捉值。 也就是说,如果URLconf捕捉到的一个命名组变量和一个额外URLconf参数包含的变量同名时,额外URLconf参数的值会被使用
from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
)
就是说任何请求(比如, /mydata/2/  或者 /mydata/432432/  )都会作 id  设置为 3  对待,不管URL里面能捕捉到什么样的值。


使用缺省视图参数

另外一个方便的特性是你可以给一个视图指定默认的参数。 这样,当没有给这个参数赋值的时候将会使用默认的值。
# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^blog/$', views.page),
    (r'^blog/page(?P<num>\d+)/$', views.page),
)

# views.py

def page(request, num='1'):
    # Output the appropriate page of blog entries, according to num.
    # ...
两个URL表达式都指向了同一个视图 views.page  

如果匹配到了第一个样式, page() 函数将会对参数 num 使用默认值 "1" ,如果第二个表达式匹配成功, page() 函数将使用正则表达式传递过来的num的值。

(注:我们已经注意到设置默认参数值是字符串 `` ‘1’`` ,不是整数`` 1`` 。为了保持一致,因为捕捉给`` num`` 的值总是字符串。

以下这个例子比提供视图配置选项一节中的例子有些许的改进。

def my_view(request, template_name='mysite/my_view.html'):
    var = do_something()
    return render_to_response(template_name, {'var': var})

特殊情况下的视图

有时你有一个模式来处理在你的URLconf中的一系列URL,但是有时候需要特别处理其中的某个URL。 在这种情况下,要使用将URLconf中把特殊情况放在首位的线性处理方式 。

比如说,你后台的帐号管理:

urlpatterns = patterns('',
    # ...
    ('^([^/]+)/([^/]+)/add/$', views.add_stage),
    # ...
)
这将匹配像 /myblog/entries/add/  和 /auth/groups/add/  这样的URL

对于用户对象的添加页面(/auth/user/add/ )

我们 可以 在视图中特别指出以解决这种情况:

def add_stage(request, app_label, model_name):
    if app_label == 'auth' and model_name == 'user':
        # do special-case code
    else:
        # do normal code
我们还有个更加优雅的方法,短路逻辑:
urlpatterns = patterns('',
    # ...
    ('^auth/user/add/$', views.user_add_stage),
    ('^([^/]+)/([^/]+)/add/$', views.add_stage),
    # ...
)
优先匹配 第一个


从URL中捕获文本

每个被捕获的参数将被作为纯Python字符串来发送,而不管正则表达式中的格式

也就是说,从url 中获取的都是string 类型,尽管python 是弱语言型,但是许多Python内建的方法对于接受的对象的类型很讲究

比如说,

所以说,在视图中应注意:

    错误的做法:

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive),
)

# views.py

import datetime

def day_archive(request, year, month, day):
    # The following statement raises a TypeError!
    date = datetime.date(year, month, day)

正确的做法:

    

def day_archive(request, year, month, day):
    date = datetime.date(int(year), int(month), int(day))

决定URLconf搜索的东西

当一个请求进来时,Django试着将请求的URL作为一个普通Python字符串进行URLconf模式匹配(而不是作为一个Unicode字符串)。 这并不包括 GET 或 POST 参数或域名。 它也不包括第一个斜杠,因为每个URL必定有一个斜杠。

such as;

    在向 http://www.example.com/myapp/ 的请求中,Django将试着去匹配 myapp/ 。在向http://www.example.com/myapp/?page=3 的请求中,Django同样会去匹配 myapp/ 

解析URLconf时,请求方法(例如, POST , GET , HEAD )并 不会 被考虑。 换而言之,对于相同的URL的所有请求方法将被导向到相同的函数中。 因此根据请求方法来处理分支是视图函数的责任。

视图函数的高级概念

请求方法的分支


# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    # ...
    (r'^somepage/$', views.some_page),
    # ...
)

# views.py

from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response

def some_page(request):
    if request.method == 'POST':
        do_something_for_post()
        return HttpResponseRedirect('/someurl/')
    elif request.method == 'GET':
        do_something_for_get()
        return render_to_response('page.html')
    else:
        raise Http404()

改进版本:

# views.py

from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response

def method_splitter(request, GET=None, POST=None):
    if request.method == 'GET' and GET is not None:
        return GET(request)
    elif request.method == 'POST' and POST is not None:
        return POST(request)
    raise Http404

def some_page_get(request):
    assert request.method == 'GET'
    do_something_for_get()
    return render_to_response('page.html')

def some_page_post(request):
    assert request.method == 'POST'
    do_something_for_post()
    return HttpResponseRedirect('/someurl/')

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    # ...
    (r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 'POST': views.some_page_post}),
    # ...
)
Assert是保证method正确,否则的话,就会抛出异常,不会继续往下执行

 method_splitter()`` ,它根据`` request.method`` 返回的值来调用相应的视图。

在URLconf中,我们把`` /somepage/`` 指到`` method_splitter()`` 函数,并把视图函数额外需要用到的`` GET`` 和`` POST`` 参数传递给它。

继续优化:

def method_splitter(request, *args, **kwargs):
    get_view = kwargs.pop('GET', None)
    post_view = kwargs.pop('POST', None)
    if request.method == 'GET' and get_view is not None:
        return get_view(request, *args, **kwargs)
    elif request.method == 'POST' and post_view is not None:
        return post_view(request, *args, **kwargs)
    raise Http404
重构method_splitter(),去掉了GET和POST两个关键字参数,改而支持使用*args和和**kwargs(注意*号) 这是一个Python特性,允许函数接受动态的、可变数量的、参数名只在运行时可知的参数。 如果你在函数定义时,只在参数前面加一个*号,所有传递给函数的参数将会保存为一个元组. 如果你在函数定义时,在参数前面加两个*号,所有传递给函数的关键字参数,将会保存为一个字典

包装视图函数

我们最终的视图技巧利用了一个高级python技术。 假设你发现自己在各个不同视图里重复了大量代码

每一个试图开始都检查request.user 是否已经认证的



def my_view1(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/')
    # ...
    return render_to_response('template1.html')

def my_view2(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/')
    # ...
    return render_to_response('template2.html')

def my_view3(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/')
    # ...
    return render_to_response('template3.html')

我们能够通过使用一个视图包装达到目的。

函数requires_login, 传入一个视图函数view, 然后返回一个新的视图函数new_view,这个新的视图函数 处理 request.user.is_authenicated(),从而决定是否 执行原来的view 函数

def requires_login(view):
    def new_view(request, *args, **kwargs):
        if not request.user.is_authenticated():
            return HttpResponseRedirect('/accounts/login/')
        return view(request, *args, **kwargs)
    return new_view


包含其他URLconf

如果你试图让你的代码用在多个基于Django的站点上,你应该考虑将你的URLconf以包含的方式来处理

例如下面的,URLconf包含了其他URLConf:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^weblog/', include('mysite.blog.urls')),
    (r'^photos/', include('mysite.photos.urls')),
    (r'^about/$', 'mysite.views.about'),
)

里有个很重要的地方: 例子中的指向 include() 的正则表达式并  包含一个 $ (字符串结尾匹配符),但是包含了一个斜杆。 每当Django遇到 include() 时,它将截断匹配的URL,并把剩余的字符串发往包含的URLconf作进一步处理。

继续看这个例子,这里就是被包含的URLconf mysite.blog.urls :

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(\d\d\d\d)/$', 'mysite.blog.views.year_detail'),
    (r'^(\d\d\d\d)/(\d\d)/$', 'mysite.blog.views.month_detail'),
)

通过这两个URLconf,下面是一些处理请求的例子:

  • /weblog/2007/ :在第一个URLconf中,模式 r'^weblog/' 被匹配。 因为它是一个 include() ,Django将截掉所有匹配的文本,在这里是 'weblog/' 。URL剩余的部分是 2007/ , 将在 mysite.blog.urls 这个URLconf的第一行中被匹配到。 URL仍存在的部分为 2007/ ,与第一行的 mysite.blog.urlsURL设置相匹配。

  • /weblog//2007/(包含两个斜杠) 在第一个URLconf中,r’^weblog/’匹配 因为它有一个include(),django去掉了匹配的部,在这个例子中匹配的部分是’weblog/’ 剩下的部分是/2007/ (最前面有一个斜杠),不匹配mysite.blog.urls中的任何一行.

    2
  • /about/ : 这个匹配第一个URLconf中的 mysite.views.about 视图。

捕获的参数如何和include()协同工作

一个被包含的URLconf接收任何来自parent URLconfs的被捕获的参数,比如:

# root urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
)

# foo/urls/blog.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^$', 'foo.views.blog_index'),
    (r'^archive/$', 'foo.views.blog_archive'),
)

在这个例子中,被捕获的 username 变量将传递给被包含的 URLconf,进而传递给那个URLconf中的 每一个视图函数。

这个被捕获的参数  总是  传递到被包含的URLconf中的  每一  行,不管那些行对应的视图是否需要这些参数。 因此,这个技术只有在你确实需要那个被传递的参数的时候才显得有用。

额外的URLconf如何和include()协同工作

你可以传递额外的URLconf选项到 include()  , 就像你可以通过字典传递额外的URLconf选项到普通的视图。 当你这样做的时候,被包含URLconf的  每一  行都会收到那些额外的参数。

第一个:

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner'), {'blogid': 3}),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive'),
    (r'^about/$', 'mysite.views.about'),
    (r'^rss/$', 'mysite.views.rss'),
)

第二个

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner')),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive', {'blogid': 3}),
    (r'^about/$', 'mysite.views.about', {'blogid': 3}),
    (r'^rss/$', 'mysite.views.rss', {'blogid': 3}),
)

这个例子和前面关于被捕获的参数一样

这个技术只有在你确实需要那个被传递的额外参数的时候才显得有用。 因为这个原因,这种技术仅当你确信在涉及到的接受到额外你给出的选项的每个URLconf时有用的才奏效。


转载于:https://my.oschina.net/swrite/blog/137100

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值