Chapter 7. Advanced Views and URLconfs

In Chapter 2, Views and URLconfs, we explained the basics of Django's view functions and URLconfs. This chapter goes into more detail about advanced functionality in those two pieces of the framework.

URLconf Tips and Tricks

There's nothing special about URLconfs-like anything else in Django, they're just Python code. You can take advantage of this in several ways, as described in the sections that follow.

Streamlining function imports

Consider this URLconf, which builds on the example in Chapter 2, Views and URLconfs:

from django.conf.urls import include, url 
from django.contrib import admin 
from mysite.views import hello, current_datetime, hours_ahead 
 
urlpatterns = [ 
      url(r'^admin/', include(admin.site.urls)), 
      url(r'^hello/$', hello), 
      url(r'^time/$', current_datetime), 
      url(r'^time/plus/(d{1,2})/$', hours_ahead), 
      ] 

As explained in Chapter 2, Views and URLconfs, each entry in the URLconf includes its associated view function, passed directly as a function object. This means it's necessary to import the view functions at the top of the module.

But as a Django application grows in complexity, its URLconf grows, too, and keeping those imports can be tedious to manage. (For each new view function, you have to remember to import it, and the import statement tends to get overly long if you use this approach.)

It's possible to avoid that tedium by importing the views module itself. This example URLconf is equivalent to the previous one:

from django.conf.urls import include, url 
from . import views 
 
urlpatterns = [ 
         url(r'^hello/$', views.hello), 
         url(r'^time/$', views.current_datetime), 
         url(r'^time/plus/(d{1,2})/$', views.hours_ahead), 
] 

Special-Casing URLs in debug mode

Speaking of constructing urlpatterns dynamically, you might want to take advantage of this technique to alter your URLconf's behavior while in Django's debug mode. To do this, just check the value of the DEBUG setting at runtime, like so:

from django.conf import settings 
from django.conf.urls import url 
from . import views 
 
urlpatterns = [ 
    url(r'^$', views.homepage), 
    url(r'^(d{4})/([a-z]{3})/$', views.archive_month), 
] 
 
if settings.DEBUG:

    urlpatterns += [url(r'^debuginfo/$', views.debug),]

In this example, the URL /debuginfo/ will only be available if your DEBUG setting is set to True.

Named groupsPreview

The above example used simple, non-named regular-expression groups (via parenthesis) to capture bits of the URL and pass them as positional arguments to a view.

In more advanced usage, it's possible to use named regular-expression groups to capture URL bits and pass them as keyword arguments to a view.

In Python regular expressions, the syntax for named regular-expression groups is (?P<name>pattern), where name is the name of the group and pattern is some pattern to match.

For example, say we have a list of book reviews on our books site, and we want to retrieve reviews for certain dates, or date ranges.

Here's a sample URLconf:

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

Tip

Notes:

To capture a value from the URL, just put parenthesis around it. There's no need to add a leading slash, because every URL has that. For example, it's ^reviews, not ^/reviews.

The 'r' in front of each regular expression string is optional but recommended. It tells Python that a string is raw-that nothing in the string should be escaped.

Example requests:

  • A request to /reviews/2005/03/ would match the third entry in the list. Django would call the function views.month_archive(request,'2005','03').
  • /reviews/2005/3/ would not match any URL patterns, because the third entry in the list requires two digits for the month.
  • /reviews/2003/ would match the first pattern in the list, not the second one, because the patterns are tested in order, and the first one is the first test to pass. Feel free to exploit the ordering to insert special cases like this.
  • /reviews/2003 would not match any of these patterns, because each pattern requires that the URL end with a slash.
  • /reviews/2003/03/03/ would match the final pattern. Django would call the function views.review_detail(request,'2003','03','03').

Here's the above example URLconf, rewritten to use named groups:

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

This accomplishes exactly the same thing as the previous example, with one subtle difference: The captured values are passed to view functions as keyword arguments rather than positional arguments. For example:

  • A request to /reviews/2005/03/ would call the function views.month_archive(request,year='2005',month='03'), instead of views.month_archive(request,'2005','03').
  • A request to /reviews/2003/03/03/ would call the function views.review_detail(request,year='2003',month='03',day='03').

In practice, this means your URLconfs are slightly more explicit and less prone to argument-order bugs-and you can reorder the arguments in your view's function definitions. Of course, these benefits come at the cost of brevity; some developers find the named-group syntax ugly and too verbose.

The matching/grouping algorithm

Here's the algorithm the URLconf parser follows, with respect to named groups vs. non-named groups in a regular expression:

  1. If there are any named arguments, it will use those, ignoring non-named arguments.
  2. Otherwise, it will pass all non-named arguments as positional arguments.

In both cases, any extra keyword arguments that have been given will also be passed to the view.

What the URLconf searches against

The URLconf searches against the requested URL, as a normal Python string. This does not include GET or POST parameters, or the domain name. For example, in a request to http://www.example.com/myapp/, the URLconf will look for myapp/. In a request to http://www.example.com/myapp/?page=3, the URLconf will look for myapp/. The URLconf doesn't look at the request method. In other words, all request methods-POST, GET, HEAD, and so on-will be routed to the same function for the same URL.

Captured arguments are always strings

Each captured argument is sent to the view as a plain Python string, regardless of what sort of match the regular expression makes. For example, in this URLconf line:

url(r'^reviews/(?P<year>[0-9]{4})/$', views.year_archive), 

...the year argument to views.year_archive() will be a string, not an integer, even though the [0-9]{4} will only match integer strings.

Specifying defaults for view arguments

A convenient trick is to specify default parameters for your view's arguments. Here's an example URLconf:

# URLconf 
from django.conf.urls import url 
 
from . import views 
 
urlpatterns = [ 
    url(r'^reviews/$', views.page), 
    url(r'^reviews/page(?P<num>[0-9]+)/$', views.page), 
] 
 
# View (in reviews/views.py) 
def page(request, num="1"): 
    # Output the appropriate page of review entries, according to num. 
    ... 

In the above example, both URL patterns point to the same view-views.page-but the first pattern doesn't capture anything from the URL. If the first pattern matches, the page() function will use its default argument for num, "1". If the second pattern matches, page() will use whatever num value was captured by the regex.

Note

Keyword Arguments vs. Positional Arguments

A Python function can be called using keyword arguments or positional arguments-and, in some cases, both at the same time. In a keyword argument call, you specify the names of the arguments along with the values you're passing. In a positional argument call, you simply pass the arguments without explicitly specifying which argument matches which value; the association is implicit in the arguments' order. For example, consider this simple function:

def sell(item, price, quantity): print "Selling %s unit(s) of %s at %s" % (quantity, item, price)

To call it with positional arguments, you specify the arguments in the order in which they're listed in the function definition:sell('Socks', '$2.50', 6)

To call it with keyword arguments, you specify the names of the arguments along with the values. The following statements are equivalent: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')

Finally, you can mix keyword and positional arguments, as long as all positional arguments are listed before keyword arguments. The following statements are equivalent to the previous examples: sell('Socks', '$2.50', quantity=6) sell('Socks', price='$2.50', quantity=6) sell('Socks', quantity=6, price='$2.50')

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset