Giter VIP home page Giter VIP logo

django-design-patterns-and-best-practices's People

Contributors

cundi avatar lielie976 avatar yueyizx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-design-patterns-and-best-practices's Issues

第四章-视图与URL

第四章-视图和URL


本章,我们会讨论以下话题:

* 基于类的和基于函数的视图
* Mixins
* 装饰器
* 常见视图模式
* 设计URL  

顶层的视图

Django中,视图是可以调用的,它接受请求,返回响应。通常它是一个函数或者一个有as_view()这样的特殊方法的类。

这两种情况下,我们创建一个普通的接受HTTPRequest作为自己的第一个参数并返回一个HTTPResponse的Python函数。URLConf也可以对这个函数传递额外的参数。这些参数由URL部分捕捉到,或者是设置了默认值。

这里是简单视图的例子:

    # In views.py
    from django.http import HttpResponse


    def hello_fn(request, name="World"):
        return HttpResponse("Hellp {}!".format(name))

这两行视图函数非常简单和好理解。目前我们还没有用request参数来做任何事情。例如,通过查看GET/POST参数, URI路径,或者REMOTE_ADDR这样的HTTP头部,我们验证请求可以更好地理解所调用视图中的上下文。

URLConf中所对应的行如下:

    # In urls.py
        url(r'^hello-fn/(?P<name>\w+)/$', views.hello_fn),
        url(r'^hello_fn/$', views.hello_fn),

我们重复使用相同的视图以支持两个URL模式。第一个模式获得了一个name参数。第二个模式没有从URL获得任何参数,这个例子中视图会使用默认的World名字。

让视图变得更高级

基于类的视图在Django 1.4中被引入。下面是之前的视图在用了同等功能的基于类的视图重写之后的样子:

    from django.views.generic import View


    class HelloView(View):
        def get(self, request, name="World"):
            return HttpResponse("Hello {}!".format(name))

同样地,对应的URLConf也有两行,一如下面命令所示:

    # In urls.py
        url(r'^hello-cl/(?P<name>\w+)/$', views.HelloView.as_view()),
        url(r'^hello-cl/$', views.HelloView.as_view()),

这个view类和我们之前的视图函数之间有多个有趣的不同点。最明显的一点就是我们需要定义一个类。接着,我们明确地定义了我们唯一要处理的GET请求。之前的视图对GETPOST做出了同样的响应,或者其他的HTTP词汇,下面是在Django shell中使用测试客户端:

    >>> from django.test import Client
    >>> c = Client()
    >>> c.get("http://0.0.0.0:8000/hello-fn/").content
    b'Hello World!'
    >>> c.post("http://0.0.0.0:8000/hello-fn/").content
    b'Hello World!'
    >>> c.get("http://0.0.0.0:8000/hello-cl/").content
    b'Hello World!'
    >>> c.post("http://0.0.0.0:8000/hello-cl/").content
    b''

从安全和可维护性的观察角度来看,还是显式更好一些。

当你需要定制视图的时候,使用类的好处才会清楚的体现出来。即,你需要改变问候内容。你可以写一个任意类型的普通视图类,并派生出所指定的问候类:

    class CreetView(View):
        greeting = "Hello {}!"
        default_name = "World"
        def get(self, request, **kwargs):
            name = kwargs.pop("name", self.default_name)
            return HttpResponse(self.greeting.format(name))


    class SuperVillianView(GreetView):
        greeting = "We are the future, {}. Not them."
        default_name = "my friend"

现在,URLConf可以引用派生的类:

    # In urls.py
        url(r'^hello-su/(?<name>\w+)/$', views.SuperVillianView.as_view()),
        url(r'^hello-su/$', views.SuperVillianView.as_view()),

按照类似的方式来定制视图函数不可行时,你需要添加多个有默认值的关键字参数。这样做很快会变得不可控制。这就是为什么通用视图从视图函数迁移到基于类的视图的原因。

Django Unchained

After spending 2 weeks hunting for good Django developers, Steve started to think out of the box. Noticing the tremendous success of their recent hackathon, he and Hart organized a Django Unchained contest at S.H.I.M. The rules were simple—build one web application a day. It could be a simple one but you cannot skip a day or break the chain. Whoever creates the longest chain, wins.

The winner—Brad Zanni was a real surprise. Being a traditional designer with hardly any programming background, he had once attended week-long Django training just for kicks. He managed to create an unbroken chain of 21 Django sites, mostly from scratch.

The very next day, Steve scheduled a 10 o' clock meeting with him at his office. Though Brad didn't know it, it was going to be his recruitment interview. At the scheduled time, there was a soft knock and a lean bearded guy in his late twenties stepped in.

As they talked, Brad made no pretense of the fact that he was not
a programmer. In fact, there was no pretense to him at all. Peering through his thick-rimmed glasses with calm blue eyes, he explained that his secret was quite simple—get inspired and then focus.
He used to start each day with a simple wireframe. He would then create an empty Django project with a Twitter bootstrap template. He found Django's generic class-based views a great way to create views with hardly any code. Sometimes, he would use a mixin or two from Django-braces. He also loved the admin interface for adding data on the go.

His favorite project was Labyrinth—a Honeypot disguised as a baseball forum. He even managed to trap a few surveillance bots hunting for vulnerable sites. When Steve explained about the SuperBook project, he was more than happy to accept the offer. The idea of creating an interstellar social network truly fascinated him.

With a little more digging around, Steve was able to find half a dozen more interesting profiles like Brad within S.H.I.M. He learnt that rather that looking outside he should have searched within the organization in the first place.

基于类的通用视图

通常,基于类的通用视图为了能更好地重复使用代码,便利用以面向对象的方法(模板方法模式)实现的视图。我讨厌术语generic views。我更乐意把它们叫做stock view。就像所储备的照片,你可以按照自己的常见需要对它们做出轻微的调整。

通用视图的产生是因为Django开发者发现他们在每一个项目中都在重复构建同类型的视图。几乎每个项目都需要有一个页面来显示一个对象的列表(List View),或者一个对象的具体内容(Detail View),有或者是一个用来创建对象的表单。依照DRY精神,这些可重复使用的视图被Django打包在一起。

这里给出Django 1.7 中通用视图速查表格:

类型 类名称 描述
基本 View 这是所有视图的父类。它执行派遣和清醒检测。
基本 TemplateView 传递模板。暴露_URLConf_的关键字到上下文。
基本 RedirectView 对任意_GET_请求做出重定向。
列表 ListView 传递任意可迭代的项,比如_queryset_。
详细 DetailView 传递基于来自_URLConf_的_pk_或者_slug_。
编辑 FormView 传递并处理表单
编辑 CreateView 传递并处理生成新对象的表单。
编辑 UpdateView 传递并处理更新对象的表单。
编辑 DeleteView 传递并处理删除对象的表单。
日期 ArchiveIndexView 传递含有日期字段的对象列表,并把最新的日期放在最前面。
日期 YearArchiveView 传递基于_URLConf_中所给定的_year_的对象列表
日期 MonthArchiveView 传递基于_year_和_month_的对象列表。
日期 WeekArchiveView 传递基于_year_和_week_数的对象列表。
日期 DayArchiveView 传递基于_year_,_month_和_day_的对象列表。
日期 TodayArchiveView 传递基于当日日期的对象列表。
日期 DateDetailView 传递一个基于_year_,month,和_day_,通过自身的_pk_或者_slug_来区分的对象。

我们已经提过类似BaseDetailView这样的基类,或是SingleObjectMixin这样的mixins。它们设计成父类。多数情况下,你不会直接地运用它们。

大多数人对基于类的视图和基于类的通用视图感到迷惑。因为它们的名称相似,但是它们并非是同一个东西。这导致一些令人担心的误会:

*通用视图仅仅是Django的一组集合*:谢天谢地,这是错的。在基于类的通用视图中是没有什么特殊魔法的。  
它们省去了你创建自己的一组基于类的通用视图的麻烦。你也可以使用类似`django-vanilla-views`这样的第三方库,它对于标准的通用视图有一个更为简单的实现方法。记住使用自定义的的通用视图或许会让其他人对你的代码感到陌生。  

*基于类的视图必须总是从一个通用视图中派生*:同样地,通用视图类也没有什么魔法。尽管,有百分之九十的时间,你都发现类似`View`的通用类当作基类来使用是最令人满意的,你免去了自己去实现相似的特性的麻烦。  

视图mixin

Mixin是在基于类的视图中按照DRY原则写代码的精髓所在。就像模型mixin一样,视图mixin得益于Python的多重继承,因此它可以很好的重复使用大部分的功能。在Python 3 中它们常常是无父类的类(或者是在Python 2 中新格式的类都派生自object)。

Mixin会在定义过的地方拦截视图的处理过程。例如,大多数的通用视图使用get_context_data设置上下文字典。它是一个插入额外上下文的好地方,比如一个用户可以查看所有指向发布文章的feed变量,一如下面命令所示:

    class FeedMixin(object):
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["feed"] = models.Post.objects.viewable_posts(self.request.user)
            return context

首先get_context_data方法在基类中调用所有与自己同名的方法来生成上下文。接下来,他用feed变量更新上下文字典。

现在,在基类的列表中,这个mixin通过包含它就可以很容易地来添加用户的订阅。这就是说,如果SuperBook需要有一个发布新文章便可被订阅的表单的典型社交网络主页,你可以像下面这样使用这个mixin:

    class MyFeed(FeedMixin, generic.CreateView):
        model = models.Post
        template_name = "myfeed.html"
        success_url = reverse_lazy("my_feed")

一个写的很好的mixin比需要非常高的要求。它应该在多数情况下都可以被灵活运用。在前面的例子中,FeedMixin会重写派生类中的feed上下文变量。如果父类要把feed用作上下文变量,它可以通过包含这个mixin发挥作用。因此,在使用mixin之前,你需要检查mixin的源代码以及其他的类,以确保没有方法或者上下文变量的冲突。

mixins的顺序

你或许见过有多个mixins的代码:

    class ComplexView(MyMixin, YourMixin, AccesMixin, DetailView):

It can get quite tricky to figure out the order to list the base classes. Like m􏰀ost things in Django, the normal rules of Python apply. Python's Method Resolution Order (MRO) determines how they should be arranged.

要找到列出基类的顺序是非常棘手的一件事。就像Django中的大多数情形一样,要用到就是Python的基本规则。Python的方法解析顺序决定了它们该如何被排列出来。

In a nutshell, m􏰀ixins co􏰀me first and base classes com􏰀e last. The m􏰀ore speciali􏰓ed the parent class is, the more it moves to the left. In practice, this is the only rule you will need to remember.

简单来说就是,把mixin放在最前面,而基类放在最后面。如果有更多的父类,这些父类都会被放到左边。

To understand why this works, consider the following simple example:

class A:
       def do(self):
           print("A")

class B:
    def do(self):
           print("B")

class BA(B, A):
       pass

class AB(A, B):
       pass

BA().do() # Prints B
AB().do() # Prints A

As you would expect, if B is mentioned before A in the list of base classes, then B's method gets called and vice versa.

Now imagine A is a base class such as CreateView and B is a mixin such as FeedMixin. The mixin is an enhancement over the basic functionality of the base class. Hence, the 􏰀ixin code should act first and in turn, call the base method if needed. So, the correct order is BA 􏰐􏰀ixins first, base last􏰑.

The order in which base classes are called can be determined by checking the __mro__ attribute of the class:

>>> AB.__mro__
 (__main__.AB, __main__.A, __main__.B, object)

So, if AB calls super(), first A gets called; then, A's super() will call B, and so on.


装饰器

Before class-based views, decorators were the only way to change the behavior of function-based views. Being wrappers around a function, they cannot change the inner working of the view, and thus effectively treat them as black boxes.

A decorator is function that takes a function and returns the decorated function. 􏰇onfused? There is so􏰀e syntactic sugar to help you. 􏰏se the annotation notation @, as shown in the following login_required decorator example:

@login_required
def simple_view(request):
       return HttpResponse()

The following code is exactly same as above:

def simple_view(request):
       return HttpResponse()
   simple_view = login_required(simple_view)

Since login_required wraps around the view, a wrapper function gets the control first. If the user was not logged in, then it redirects to settings.LOGIN_URL. Otherwise, it executes simple_view as if it did not exist.
Decorators are less 􏰈exible than 􏰀ixins. However, they are si􏰀pler. You can
use both decorators and mixins in Django. In fact, many mixins are implemented with decorators.

视图模式

Let's take a look at some common design patterns seen in designing views.

模式-访问控制视图

问题:页面需要基于用户是否登录,是否是站点成员,或者其他的任何条件,按照条件访问。

解决方法:使用mixins或者装饰器控制到视图的访问。

问题细节

大多数的网站都有当你登录才能访问的页面。另外的一些页面可以被匿名访问或者普通游客访问。如果一个匿名访客视图访问一个面向已登录用户的页面,它们会被路由到登录页面。理论上,在登录后,他们应该被路由回第一次想要见到的页面。

方案详情

有两个控制到一个视图的访问方法:

1. 通过对基于函数视图或者基于类视图使用一个装饰器实现控制:  
@login_required(MyView.as_view())
2. 通过覆盖mixin的类视图的`dispatch`方法实现控制:  
 class LoginRequiredMixin:
           @method_decorator(login_required)
           def dispatch(self, request, *args, **kwargs):
               return super().dispatch(request, *args, **kwargs)
这里我们真的不需要装饰器。更为明确地形式建议如下:  
class LoginRequiredMixin:
           def dispatch(self, request, *args, **kwargs):
               if not request.user.is_authenticated():
                   raise PermissionDenied
               return super().dispatch(request, *args, **kwargs)

当异常PermissionDenied抛出时,Django会在根目录中显示403.html模板,如果模板不存在,就会出现“403 Forbidden”页面。

当然,

这里使用它们控制到登录和匿名访问的视图:

from braces.views import LoginRequiredMixin, AnonymousRequiredMixin
   class UserProfileView(LoginRequiredMixin, DetailView):
       # This view will be seen only if you are logged-in
       pass
   class LoginFormView(AnonymousRequiredMixin, FormView):
       # This view will NOT be seen if you are loggedin
       authenticated_redirect_url = "/feed"

Django中站点成员是使用设置在用户模型中的is_staff标识的用户。
同样地,你可以使用称做UserPassesTestMixin的django-braces mixin:

    from braces.views import UserPassesTestMixin

    class SomeStaffView(UserPassesTestMixin, TemplateView):
        def test_func(self, user):
            return user.is_staff

你也可以创建执行特定检查的mixins,比如对象是否被原作者或者其他人(使用登录用户比对)编辑:

    class CheckOwnerMixin:
        # 被用于派生自SingleObjectMixin的类
        def get_object(self, queryset=None):
            obj = super().get_object(queryset)
            if not obj.owner == self.request.user:
                raise PermissionDenied
            return obj

模式-上下文加强器

问题:多个基于类的通用视图需要相同的上下文变量。

解决方法:创建一个设置为共享上下文变量的mixin。

问题细节

Django templates can only show variables that are present in its context dictionary. However, sites need the same information in several pages. For instance, a sidebar showing the recent posts in your feed might be needed in several views.

However, if we use a generic class-based view, we would typically have a limited set of context variables related to a specific 􏰀odel. 􏰊etting the sa􏰀e context variable in each view is not DRY.

方案详情

Most generic class-based views are derived from ContextMixin. It provides
the get_context_data method, which most classes override, to add their own context variables. While overriding this method, as a best practice, you will need to call get_context_data of the superclass first and then add or override your context variables.

We can abstract this in the form of a mixin, as we have seen before:

class FeedMixin(object):
       def get_context_data(self, **kwargs):
           context = super().get_context_data(**kwargs)
           context["feed"] = models.Post.objects.viewable_posts(self.
   request.user)
           return context

We can add this mixin to our views and use the added context variables in our te􏰀plates. 􏰍otice that we are using the 􏰀odel 􏰀anager defined in Chapter 3, Models, to filter the posts.

A more general solution is to use StaticContextMixin from django-braces for static-context variables. For example, we can add an additional context variable latest_profile that contains the latest user to join the site:

class CtxView(StaticContextMixin, generic.TemplateView):
       template_name = "ctx.html"
       static_context = {"latest_profile": Profile.objects.latest('pk')}

Here, static context means anything that is unchanged from a request to request. In that sense, you can mention QuerySets as well. However, our feed context variable needs self.request.user to retrieve the user's viewable posts. Hence, it cannot be included as a static context here.

模式-服务

问题:Information from your website is often scraped and processed by other applications.

解决方法:创建返回机器友好的格式的轻量的服务,比如JSON或者XML。

问题细节

We often forget that websites are not just used by hu􏰀ans. 􏰆 significant percentage of web traffic co􏰀es fro􏰀 other progra􏰀s like crawlers, bots, or scrapers. Sometimes, you will need to write such programs yourself to extract information from another website.

Generally, pages designed for human consumption are cumbersome for mechanical extraction. HTML pages have information surrounded by markup, requiring extensive cleanup. Sometimes, information will be scattered, needing extensive data collation and transformation.

􏰆 􏰀achine interface would be ideal in such situations. You can not only reduce the hassle of extracting information but also enable the creation of mashups. The longevity of an application would be greatly increased if its functionality is exposed in a machine-friendly manner.

方案详情

Service-oriented architecture (SOA) has popularized the concept of a service. A service is a distinct piece of functionality exposed to other applications as a service. For example, Twitter provides a service that returns the most recent public statuses.

A service has to follow certain basic principles:

• Statelessness: This avoids the internal state by externalizing state information
• Loosely coupled: This has fewer dependencies and a minimum of assumptions
• Composable: This should be easy to reuse and combine with other services

In Django, you can create a basic service without any third-party packages. Instead of returning HTML, you can return the serialized data in the JSON format. This form of a service is usually called a web Application Programming Interface (API).

For exa􏰀ple, we can create a si􏰀ple service that returns five recent public posts from SuperBook as follows:

class PublicPostJSONView(generic.View):
       def get(self, request, *args, **kwargs):
           msgs = models.Post.objects.public_posts().values(
               "posted_by_id", "message")[:5]
           return HttpResponse(list(msgs), content_type="application/json")

For a more reusable implementation, you can use the JSONResponseMixin class
from django-braces to return JSON using its render_json_response method:

from braces.views import JSONResponseMixin
   class PublicPostJSONView(JSONResponseMixin, generic.View):
       def get(self, request, *args, **kwargs):
           msgs = models.Post.objects.public_posts().values("posted_by_id", "message")[:5]
           return self.render_json_response(list(msgs))

If we try to retrieve this view, we will get a JSON string rather than an HTML response:

>>> from django.test import Client
>>> Client().get("http://0.0.0.0:8000/public/").content
b'[{"posted_by_id": 23, "message": "Hello!"},
   {"posted_by_id": 13, "message": "Feeling happy"},
   ...

Note that we cannot pass the QuerySet method directly to render the JSON response. It has to be a list, dictionary, or any other basic Python built-in data type recognized by the JSON serializer.

Of course, you will need to use a package such as Django REST framework if you need to build anything more complex than this simple API. Django REST framework takes care of serializing (and deserializing) QuerySets, authentication, generating
a web-browsable API, and many other features essential to create a robust and full􏰉􏰈edged 􏰆PI.

设计URL

Django has one of the 􏰀ost 􏰈exible 􏰏RL sche􏰀es a􏰀ong web fra􏰀eworks. Basically, there is no i􏰀plied 􏰏RL sche􏰀e. You can explicitly define any 􏰏RL scheme you like using appropriate regular expressions.

However, as superheroes love to say—"With great power comes great responsibility.􏰎 You cannot get away with a sloppy 􏰏RL design any 􏰀more.

URLs used to be ugly because they were considered to be ignored by users. Back in the 90s when portals used to be popular, the common assumption was that your users will come through the front door, that is, the home page. They will navigate to the other pages of the site by clicking on links.

Search engines have changed all that. According to a 2013 research report, nearly half (47 percent) of all visits originate from a search engine. This means that any page in your website, depending on the search relevance and popularity can be the first page your user sees. 􏰆ny 􏰏RL can be the front door.

More importantly, Browsing 101 taught us security. Don't click on a blue link in the wild, we warn beginners. Read the 􏰏RL first. Is it really your bank's 􏰏RL or a site trying to phish your login details?

Today, URLs have become part of the user interface. They are seen, copied, shared, and even edited. Make them look good and understandable from a glance. No more eye sores such as:

http://example.com/gallery/default.asp?sid=9DF4BC0280DF12D3ACB6009027
1E26A8&command=commntform

Short and meaningful URLs are not only appreciated by users but also by search engines. URLs that are long and have less relevance to the content adversely affect your site's search engine rankings.

Finally, as implied by the maxim "Cool URIs don't change," you should try to maintain your URL structure over time. Even if your website is completely redesigned, your old links should still work. Django makes it easy to ensure that this is so.

Before we delve into the details of designing URLs, we need to understand the structure of a URL.

URL解剖

Technically, URLs belong to a 􏰀ore general fa􏰀ily of identifiers called Uniform Resource Identifiers (URIs). Hence, a URL has the same structure as a URI.

A URI is composed of several parts:

URI = Scheme + Net Location + Path + Query + Fragment

For example, a URI (http://dev.example.com:80/gallery/ videos?id=217#comments) can be deconstructed in Python using the urlparse function:

>>> from urllib.parse import urlparse
>>> urlparse("http://dev.example.com:80/gallery/videos?id=217#comments")
ParseResult(scheme='http', netloc='dev.example.com:80', path='/gallery/
videos', params='', query='id=217', fragment='comments')

The URI parts can be depicted graphically as follows:

2015-05-28 15 33 49

Even though Django documentation prefers to use the term URLs, it might more technically correct to say that you are working with URIs most of the time. We will use the terms interchangeably in this book.

Django URL patterns are mostly concerned about the 'Path' part of the URI. All other parts are tucked away.

url.py中发生了什么?

It is often helpful to consider urls.py as the entry point of your project. It is usually the first file I open when I study a Django project. 􏰋ssentially, urls.py contains the root 􏰏RL configuration or URLConf of the entire project.

It would be a Python list returned from patterns assigned to a global variable called urlpatterns. Each incoming URL is matched with each pattern from top to botto􏰀 in a sequence. In the first 􏰀atch, the search stops, and the request is sent to the corresponding view.

Here, in considerably si􏰀plified for􏰀, is an excerpt of urls.py from Python.org, which was recently rewritten in Django:

 urlpatterns = patterns(
       '',
       # Homepage
       url(r'^$', views.IndexView.as_view(), name='home'),
       # About
       url(r'^about/$',
           TemplateView.as_view(template_name="python/about.html"),
           name='about'),
       # Blog URLs
       url(r'^blogs/', include('blogs.urls', namespace='blog')),
       # Job archive
       url(r'^jobs/(?P<pk>\d+)/$',
           views.JobArchive.as_view(),
           name='job_archive'),
       # Admin
       url(r'^admin/', include(admin.site.urls)),
   )

Some interesting things to note here are as follows:

• The first argu􏰀ent of the patterns function is the prefix. It is usually blank
for the root URLConf. The remaining arguments are all URL patterns.
• Each URL pattern is created using the url function, which takes five arguments. Most patterns have three arguments: the regular expression pattern, view callable, and name of the view.
• The about pattern defines the view by directly instantiating TemplateView. Some hate this style since it mentions the implementation, thereby violating separation of concerns.
• Blog 􏰏RLs are 􏰀entioned elsewhere, specifically in urls.py inside the blogs app. In general, separating an app's 􏰏RL pattern into its own file is good practice.
• The jobs pattern is the only example here of a named regular expression.

In future versions of Django, urlpatterns should be a plain list of URL pattern objects rather than arguments to the patterns function. This is great for sites with lots of patterns, since urlpatterns being a function can accept only a maximum of 255 arguments.

If you are new to Python regular expressions, you 􏰀ight find the pattern syntax to be slightly cryptic. Let's try to demystify it.

URL模式语法

URL regular expression patterns can sometimes look like a confusing mass of punctuation marks. However, like most things in Django, it is just regular Python.

It can be easily understood by knowing that URL patterns serve two functions: to match URLs appearing in a certain form, and to extract the interesting bits from a URL.

The first part is easy. If you need to 􏰀atch a path such as /jobs/1234, then just use the "^jobs/\d+"pattern (here \d stands for a single digit from 0 to 9). Ignore the leading slash, as it gets eaten up.
The second part is interesting because, in our example, there are two ways of extracting the job ID (that is, 1234), which is required by the view.

The simplest way is to put a parenthesis around every group of values to be captured. Each of the values will be passed as a positional argument to the view. For example, the "^jobs/(\d+)" pattern will send the value "1234" as the second argu􏰀ent 􏰐the first being the request􏰑 to the view.

The problem with positional arguments is that it is very easy to mix up the order. Hence, we have name-based arguments, where each captured value can be named. Our example will now look like "^jobs/(?P<pk>\d+)/". This means that the view will be called with a keyword argument pk being equal to "1234".

If you have a class-based view, you can access your positional arguments in self. args and name-based arguments in self.kwargs. Many generic views expect their arguments solely as name-based arguments, for example, self.kwargs["slug"].

Mnemonic – parents question pink action-figures

I admit that the syntax for name-based arguments is quite difficult to remember. Often, I use a simple mnemonic as a memory aid. The phrase "Parents Question Pink Action-figures" stands for the first letters of Parenthesis, Question mark, (the letter) P, and Angle brackets.

Put them together and you get (?P< . You can enter the name of the pattern and figure out the rest yourself.

It is a handy trick and really easy to remember. Just imagine a furious parent holding a pink-colored hulk action figure.

Another tip is to use an online regular expression generator such as http://pythex. org/ or https://www.debuggex.com/tocraftandtestyourregularexpressions.

命名和命名空间

Always name your patterns. It helps in decoupling your code from the exact URL paths. For instance, in the previous URLConf, if you want to redirect to the about page, it might be tempting to use redirect("/about"). Instead, use redirect("about"), as it uses the name rather than the path.
Here are some more examples of reverse lookups:

>>> from django.core.urlresolvers import reverse
>>> print(reverse("home"))
"/"
>>> print(reverse("job_archive", kwargs={"pk":"1234"}))
"jobs/1234/"

Names must be unique. If two patterns have the same name, they will not work. So, some Django packages used to add prefixes to the pattern name. For example, an application named blog might have to call its edit view as 'blog-edit' since 'edit' is a common name and might cause conflict with another application.

Namespaces were created to solve such problems. Pattern names used in a namespace have to be only unique within that namespace and not the entire project. It is recommended that you give every app its own namespace. For example, we can create a 'blog' namespace with only the blog's URLs by including this line in
the root URLconf:

   url(r'^blog/', include('blog.urls', namespace='blog')),

Now the blog app can use pattern names, such as 'edit' or anything else as long as they are unique within that app. While referring to a name within a namespace, you will need to mention the namespace, followed by a ':' before the name. It would be"blog:edit"in our example.

As Zen of Python says—"Namespaces are one honking great idea—let's do more of those." You can create nested namespaces if it makes your pattern names cleaner, such as "blog:comment:edit". I highly recommend that you use namespaces in your projects.

模式顺序

Order your patterns to take advantage of how Django processes them, that is, top-down. A good rule of thumb is to keep all the special cases at the top. Broader patterns can be mentioned further down. The broadest—a catch-all—if present, can go at the very end.
For example, the path to your blog posts might be any valid set of characters, but you might want to handle the About page separately. The right sequence of patterns should be as follows:

   urlpatterns = patterns(
       '',
       url(r'^about/$', AboutView.as_view(), name='about'),
       url(r'^(?P<slug>\w+)/$', ArticleView.as_view(), name='article'),
   )  

If we reverse the order, then the special case, the AboutView, will never get called.

URL模式风格

Designing URLs of a site consistently can be easily overlooked. Well-designed URLs can not only logically organize your site but also make it easy for users to guess paths. Poorly designed ones can even be a security risk: say, using a database ID (which occurs in a monotonic increasing sequence of integers) in a URL pattern can increase the risk of information theft or site ripping.

Let's examine some common styles followed in designing URLs.

分布存储URL

Some sites are laid out like Departmental stores. There is a section for Food, inside which there would be an aisle for Fruits, within which a section with different varieties of Apples would be arranged together.
In the case of URLs, this means that you will find these pages arranged hierarchically as follows:

   http://site.com/ <section> / <sub-section> / <item>  

The beauty of this layout is that it is so easy to climb up to the parent section. Once you remove the tail end after the slash, you are one level up.

For example, you can create a similar structure for the articles section, as shown here:

   # project's main urls.py
   urlpatterns = patterns(
'',
       url(r'^articles/$', include(articles.urls), namespace="articles"),
   )
   # articles/urls.py
   urlpatterns = patterns(
       '',
       url(r'^$', ArticlesIndex.as_view(), name='index'),
       url(r'^(?P<slug>\w+)/$', ArticleView.as_view(), name='article'),
)  

Notice the 'index' pattern that will show an article index in case a user climbs up from a particular article.

RESTful URLs

In 2000, Roy Fielding introduced the term Representational state transfer (REST) in his doctoral dissertation. Reading his thesis (http://www.ics.uci.edu/~fielding/ pubs/dissertation/top.htm) is highly recommended to better understand the architecture of the web itself. It can help you write better web applications that do not violate the core constraints of the architecture.

One of the key insights is that a URI is an identifier to a resource. A resource can be anything, such as an article, a user, or a collection of resources, such as events. Generally speaking, resources are nouns.

The web provides you with some fundamental HTTP verbs to manipulate resources: GET, POST, PUT, PATCH, and DELETE. Note that these are not part of the URL itself. Hence, if you use a verb in the URL to manipulate a resource, it is a bad practice.

For example, the following URL is considered bad:

http://site.com/articles/submit/  

Instead, you should remove the verb and use the POST action to this URL:

http://site.com/articles/  

Best Practice

Keep verbs out of your URLs if HTTP verbs can be used instead.

Note that it is not wrong to use verbs in a URL. The search URL for your site can have the verb 'search' as follows, since it is not associated with one resource as per REST:

http://site.com/search/?q=needle  

RESTful URLs are very useful for designing CRUD interfaces. There is almost a one-to-one mapping between the Create, Read, Update, and Delete database operations and the HTTP verbs.

Note that the RESTful URL style is complimentary to the departmental store URL style. Most sites mix both the styles. They are separated for clarity and better understanding.

下载练习代码

You can download the example code fies for all Packt books you have purchasedfrom your account at http://www.packtpub.com. If you purchased this bookelsewhere, you can visit http://www.packtpub. com/support and register tohave the fies e-mailed directly to you. Pull requests and bug reports to the SuperBook project can be sent to https://github.com/DjangoPatternsBook/superbook.

总结

Views are an extremely powerful part of the MVC architecture in Django. Over time, class-based views have proven to be more flexible and reusable compared to traditional function-based views. Mixins are the best examples of this reusability.

Django has an extremely flexible URL dispatch system. Crafting good URLs takes into account several aspects. Well-designed URLs are appreciated by users too.
In the next chapter, we will take a look at Django's templating language and how best to leverage it.


© Creative Commons BY-NC-ND 3.0 | 我要订阅 | 我要捐助

第九章-测试与调试

第九章 测试与调试


本章,我们将讨论以下话题:

• Test-driven development
• Dos and don'ts of writing tests
• Mocking
• Debugging
• Logging

Every programmer must have, at least, considered skipping writing tests. In Django, the default app layout has a tests.py module with some placeholder content. It is a reminder that tests are needed. However, we are often tempted to skip it.

In Django, writing tests is quite similar to writing code. In fact, it is practically code. So, the process of writing tests might seem like doubling (or even more) the effort of coding. Sometimes, we are under so much time pressure that it might seem ridiculous to spend time writing tests when we are just trying to make things work.

However, eventually, it is pointless to skip tests if you ever want anyone else to use your code. Imagine that you invented an electric razor and tried to sell it to your friend saying that it worked well for you, but you haven't tested it properly. Being a good friend of yours he or she might agree, but imagine the horror if you told this to a stranger.

Why write tests?

Tests in a software check whether it works as expected. Without tests, you might be able to say that your code works, but you will have no way to prove that it works correctly.

Additionally, it is important to remember that it can be dangerous to omit unit testing in Python because of its duck-typing nature. Unlike languages such as Haskell, type checking cannot be strictly enforced at compile time. Unit tests, being run at runtime (although in a separate execution), are essential in Python development.

Writing tests can be a humbling experience. The tests will point out your mistakes and you will get a chance to make an early course correction. In fact, there are some who advocate writing tests before the code itself.

Test-driven development

Test-driven development (TDD) is a for􏰀 of software develop􏰀ent where you first write the test, run the test 􏰋which would fail first􏰌, and then write the 􏰀ini􏰀u􏰀 code needed to make the test pass. This might sound counter-intuitive. Why do we need to write tests when we know that we have not written any code and we are certain that it will fail because of that?

However, look again. We do eventually write the code that 􏰀erely satisfies these tests. This 􏰀eans that these tests are not ordinary tests, they are 􏰀ore like specifications. They tell you what to expect. These tests or specifications will directly co􏰀e fro􏰀 your client's user stories. You are writing just enough code to 􏰀ake it work.

The process of test􏰃driven develop􏰀ent has 􏰀any si􏰀ilarities to the scientific 􏰀ethod, which is the basis of 􏰀odern science. In the scientific 􏰀ethod, it is i􏰀portant to fra􏰀e the hypothesis first, gather data, and then conduct experi􏰀ents that are repeatable and verifiable to prove or disprove your hypothesis.

My recommendation would be to try TDD once you are comfortable writing tests for your projects. Beginners 􏰀ight find it difficult to fra􏰀e a test case that checks how the code should behave. For the same reasons, I wouldn't suggest TDD for exploratory programming.

Writing a test case

There are different kinds of tests. However, at the minimum, a programmers need to know unit tests since they have to be able to write them. Unit testing checks the smallest testable part of an application. Integration testing checks whether these parts work well with each other.

The word unit is the key term here. Just test one unit at a time. Let's take a look at a simple example of a test case:

# tests.py
   from django.test import TestCase
   from django.core.urlresolvers import resolve
   from .views import HomeView
   class HomePageOpenTestCase(TestCase):
       def test_home_page_resolves(self):
           view = resolve('/')
           self.assertEqual(view.func.__name__,
                            HomeView.as_view().__name__)

This is a simple test that checks whether, when a user visits the root of our website's domain, they are correctly taken to the home page view. Like most good tests, it has a long and self-descriptive name. The test simply uses Django's resolve() function to match the view callable mapped to the "/" root location to the known view function by their names.

It is more important to note what is not done in this test. We have not tried to retrieve the HTML contents of the page or check its status code. We have restricted ourselves to test just one unit, that is, the resolve() function, which maps the URL paths to view functions.

Assuming that this test resides in, say, app1 of your project, the test can be run with the following command:

 $ ./manage.py test app1
   Creating test database for alias 'default'...
   .
   -----------------------------------------------------------------
   Ran 1 test in 0.088s
   OK
   Destroying test database for alias 'default'...

This command runs all the tests in the app1 application or package. The default test runner will look for tests in all modules in this package matching the pattern test*.py.

Django now uses the standard unittest module provided by Python rather than bundling its own. You can write a testcase class by subclassing from django.test.TestCase. This class typically has methods with the following naming convention:

• setUp (optional): This method will be run before each test method. It can be used to create common objects or perform other initialization tasks that bring your test case to a known state.
• tearDown (optional): This method will be run after a test method, irrespective of whether the test passed or not. Clean-up tasks are usually performed here.

A test case is a way to logically group test methods, all of which test a scenario. When all the test methods pass (that is, do not raise any exception), then the test case is considered passed. If any of them fail, then the test case fails.

The assert method

Each test method usually invokes an assert*() method to check some expected outco􏰀e of the test. In our first exa􏰀ple, we used assertEqual() to check whether the function name matches with the expected function.

Similar to assertEqual(), the Python 3 unittest library provides more than 32 assert 􏰀ethods. It is further extended by Django by 􏰀ore than 􏰛􏰧 fra􏰀ework􏰃specific assert 􏰀ethods. You 􏰀ust choose the 􏰀ost appropriate 􏰀ethod based on the end outcome that you are expecting so that you will get the most helpful error message.

Let's see why by looking at an example testcase that has the following setUp() method:

def setUp(self):
       self.l1 = [1, 2]
       self.l2 = [1, 0]

Our test is to assert that l1 and l2 are equal (and it should fail, given their values). Let's take a look at several equivalent ways to accomplish this:

表格:略

The first state􏰀ent uses Python's built􏰃 in assert keyword. Notice that it throws the least helpful error. You cannot infer what values or types are in the self.l1 and self.l2 variables. This is primarily the reason why we need to use the assert*() methods.

Next, the exception thrown by assertEqual() very helpfully tells you that you are comparing two lists and even tells you at which position they begin to differ. This is exactly similar to the exception thrown by the more specialized assertListEqual() function. This is because, as the documentation would tell you, if assertEqual() is given two lists for comparison, then it hands it over to assertListEqual().

Despite this, as the last example proves, it is always better to use the 􏰀ost specific assert* method for your tests. Since the second argument is not a list, the error clearly tells you that a list was expected.

􏰘se the 􏰀ost specific assert* method in your tests.

Therefore, you need to familiarize yourself with all the assert methods, and choose the 􏰀ost specific one to evaluate the result you expect. This also applies to when you are checking whether your application does not do things it is not supposed
to do, that is, a negative test case. You can check for exceptions or warnings using assertRaises and assertWarns respectively.

Writing better test cases

We have already seen that the best test cases test a small unit of code at a time. They also need to be fast. A programmer needs to run tests at least once before every commit to the source control. Even a delay of a few seconds can tempt a programmer to skip running tests (which is not a good thing).

Here are some qualities of a good test case (which is a subjective term, of course) in the form of an easy-to-remember mnemonic "F.I.R.S.T. class test case":

1. Fast: the faster the tests, the more often they are run. Ideally, your tests should complete in a few seconds.
2. Independent: Each test case must be independent of others and can be run in any order.
3. Repeatable: The results must be the same every time a test is run. Ideally, all random and varying factors must be controlled or set to known values before a test is run.
4. Small: Test cases must be as short as possible for speed and ease of understanding.
5. Transparent: Avoid tricky implementations or ambiguous test cases.

Additionally, make sure that your tests are automatic. Eliminate any manual steps, no matter how small. Automated tests are more likely to be a part of your team's work􏰁ow and easier to use for tooling purposes.

Perhaps, even more important are the don'ts to remember while writing test cases:

• Do not (re)test the framework: Django is well tested. Don't check for URL lookup, template rendering, and other framework-related functionality.
• Do not test implementation details: Test the interface and leave the minor implementation details. It makes it easier to refactor this later without breaking the tests.
• Test models most, templates least: Templates should have the least business logic, and they change more often.
• Avoid HTML output validation: Test views use their context variable's output rather than its HTML-rendered output.
• Avoid using the web test client in unit tests: Web test clients invoke several components and are therefore, better suited for integration tests.
• Avoid interacting with external systems: Mock them if possible. Database is an exception since test database is in-memory and quite fast.

Of course, you can (and should) break the rules where you have a good reason to 􏰋just like I did in 􏰀y first exa􏰀ple􏰌. 􏰘lti􏰀ately, the 􏰀ore creative you are at writing tests, the earlier you can catch bugs, and the better your application will be.

Mocking

Most real-life projects have various interdependencies between components. While testing one component, the result must not be affected by the behavior of other components. For example, your application might call an external web service that might be unreliable in terms of network connection or slow to respond.

Mock objects imitate such dependencies by having the same interface, but they respond to method calls with canned responses. After using a mock object in a test, you can assert whether a certain method was called and verify that the expected interaction took place.

Take the exa􏰀ple of the 􏰆uperHero profile eligibility test 􏰀entioned in Pattern: Service objects (see Chapter 3, Models). We are going to mock the call to the service object method in a test using the Python 3 unittest.mock library:

 # profiles/tests.py
   from django.test import TestCase
   from unittest.mock import patch
   from django.contrib.auth.models import User
   class TestSuperHeroCheck(TestCase):
       def test_checks_superhero_service_obj(self):
           with patch("profiles.models.SuperHeroWebAPI") as ws:
               ws.is_hero.return_value = True
               u = User.objects.create_user(username="t")
               r = u.profile.is_superhero()
           ws.is_hero.assert_called_with('t')
           self.assertTrue(r)

Here, we are using patch() as a context manager in a with statement. Since the profile 􏰀odel's is_superhero() method will call the SuperHeroWebAPI.is_hero() class method, we need to mock it inside the models module. We are also hard-coding the return value of this method to be True.

The last two assertions check whether the method was called with the correct arguments and if is_hero() returned True, respectively. Since all methods of SuperHeroWebAPI class have been mocked, both the assertions will pass.

Mock objects come from a family called Test Doubles, which includes stubs, fakes, and so on. Like movie doubles who stand in for real actors, these test doubles are used in place of real objects while testing. While there are no clear lines drawn between them, Mock objects are objects that can test the behavior, and stubs are simply placeholder implementations.

Pattern 􏰐 test fi􏰫tures 􏰏and f􏰏actories

Problem: Testing a component requires the creation of various prerequisite objects before the test. Creating them explicitly in each test method gets repetitive.

Solution: Utilize factories or fixtures to create the test data objects.

Problem details

Before running each test, Django resets the database to its initial state, as it would be after running migrations. Most tests will need the creation of some initial objects to set the state. Rather than creating different initial objects for different scenarios, a common set of initial objects are usually created.

This can quickly get unmanageable in a large test suite. The sheer variety of such initial objects can be hard to read and later understand. This leads to hard􏰃to􏰃find bugs in the test data itself!

Being such a common problem, there are several means to reduce the clutter and write clearer test cases.

Solution details

The first solution we will take a look at is what is given in the Django docu􏰀entation itself􏰩test fixtures. Here, a test fixture is a file that contains a set of data that can be i􏰀ported into your database to bring it to a known state. Typically, they are Y􏰅ML or 􏰈􏰆􏰚􏰉 files previously exported from the same database when it had some data.

For exa􏰀ple, consider the following test case, which uses a test fixture􏰂:

from django.test import TestCase
   class PostTestCase(TestCase):
       fixtures = ['posts']
       def setUp(self):
           # Create additional common objects
           pass
       def test_some_post_functionality(self):
           # By now fixtures and setUp() objects are loaded
           pass

Before setUp() gets called in each test case, the specified fixture, posts gets loaded. Roughly speaking, the fixture would be searched for in the fixtures directory with certain known extensions, for example, app/fixtures/posts.json.

However, there are a nu􏰀ber of proble􏰀s with fixtures. Fixtures are static snapshots of the database. They are schema-dependent and have to be changed each time your models change. They also might need to be updated when your test-case assertions change. 􏰘pdating a large fixture file 􏰀anually, with 􏰀ultiple related objects,
is no joke.

For all these reasons, 􏰀any consider using fixtures as an anti􏰃pattern. It is recommended that you use factories instead. A factory class creates objects of a particular class that can be used in tests. It is a DRY way of creating initial test objects.

Let's use a model's objects.create method to create a simple factory:

from django.test import TestCase
   from .models import Post
   class PostFactory:
       def make_post(self):
           return Post.objects.create(message="")
   class PostTestCase(TestCase):
       def setUp(self):
           self.blank_message = PostFactory().makePost()
       def test_some_post_functionality(self):
           pass

􏰖o􏰀pared to using fixtures, the initial object creation and the test cases are all in one place. Fixtures load static data as is into the database without calling 􏰀odel􏰃defined save() methods. Since factory objects are dynamically generated, they are more likely to run through your application's custom validations.

However, there is a lot of boilerplate in writing such factory classes yourself. The factory_boy package, based on thoughtbot's factory_girl, provides a declarative syntax for creating object factories.

Rewriting the previous code to use factory_boy, we get the following result:

import factory
   from django.test import TestCase
   from .models import Post
   class PostFactory(factory.Factory):
       class Meta:
           model = Post
       message = ""
   class PostTestCase(TestCase):
       def setUp(self):
           self.blank_message = PostFactory.create()
           self.silly_message = PostFactory.create(message="silly")
       def test_post_title_was_set(self):
           self.assertEqual(self.blank_message.message, "")
           self.assertEqual(self.silly_message.message, "silly")

Notice how clear the factory class becomes when written in a declarative fashion. The attribute's values do not have to be static. You can have sequential, rando􏰀, or computed attribute values. If you prefer to have more realistic placeholder data such as US addresses, then use the django-faker package.

In conclusion, I would recommend factories, especially factory_boy, for most projects that need initial test objects. One 􏰀ight still want to use fixtures for static data, such as lists of countries or t-shirt sizes, since they would rarely change.

Dire Predictions

After the announcement of the impossible deadline, the entire team seemed to be suddenly out of time. They went from 4-week scrum sprints to 1-week sprints. Steve wiped every meeting off their calendars except "today's 30-minute catch-up with Steve." He preferred to have a one-on-one discussion if he needed to talk to someone at their desk.

At Madam O's insistence, the 30-minute meetings were held at a sound proof hall 20 levels below the S.H.I.M. headquarters. On Monday, the team stood around a large circular table with a gray metallic surface like the rest of the room. Steve stood awkwardly in front of it and made a stiff waving gesture with an open palm.

Even though everyone had seen the holographs come alive before, it never failed to amaze them each time. The disc almost segmented itself into hundreds of metallic squares and rose like miniature skyscrapers in a futuristic model city. It took them a second to realize that they were looking at a 3D bar chart.

"Our burn-down chart seems to be showing signs of slowing down. I am guessing it is the outcome of our recent user tests, which is a good thing. But..." Steve's face seemed to show the strain of trying to sti􏰁e a snee􏰄e. He gingerly 􏰁icked his forefinger upwards in the air and the chart smoothly extended to the right.

"At this rate, projections indicate that we will miss the go-live
by several days, at best. I did a bit of analysis and found several critical bugs late in our development. We can save a lot of time and effort if we can catch them early. I want to put your heads together and come up with some i..."

Steve clasped his mouth and let out a loud sneeze. The holograph interpreted this as a sign to zoom into a particularly uninteresting part of the graph. Steve cursed under his breath and turned it
off. He borrowed a napkin and started noting down everyone's suggestions with an ordinary pen.

One of the suggestions that Steve liked most was a coding checklist listing the most common bugs, such as forgetting to apply migrations. He also liked the idea of involving users earlier in
the development process for feedback. He also noted down some unusual ideas, such as a Twitter handle for tweeting the status of the continuous integration server.

At the close of the meeting, Steve noticed that Evan was missing. 􏰇Where is 􏰍van?􏰇 he asked. 􏰇􏰉o idea,􏰇 said Brad looking confused, "he was here a minute ago."

Learning more about testing

Django's default test runner has improved a lot over the years. However, test runners such as py.test and nose are still superior in terms of functionality.
They make your tests easier to write and run. Even better, they are compatible
with your existing test cases.

You 􏰀ight also be interested in knowing what percentage of your code is covered by tests. This is called Code coverage and coverage.py is a very popular tool for finding this out.

Most projects today tend to use a lot of JavaScript functionality. Writing tests for them usually require a browser-like environment for execution. Selenium is a great browser automation tool for executing such tests.

While a detailed treatment of testing in Django is outside the scope of this book, I would strongly recommend that you learn more about it.

If nothing else, the two main takeaways I wanted to convey through this section are first, write tests, and second, once you are confident at writing the􏰀, practice TDD.

Debugging

Despite the most rigorous testing, the sad reality is, we still have to deal with bugs. Django tries its best to be as helpful as possible while reporting an error to help you in debugging. However, it takes a lot of skill to identify the root cause of the problem.

Thankfully, with the right set of tools and techniques, we can not only identify the bugs but also gain great insight into the runtime behavior of your code. Let's take a look at some of these tools.

Django debug page

If you have encountered any exception in development, that is, when DEBUG=True, then you would have already seen an error page similar to the following screenshot:

图片:略

Since it comes up so frequently, most developers tend to miss the wealth of information in this page. Here are some places to take a look at:


• Exception details: Obviously, you need to read what the exception tells you very carefully.
• Exception location: This is where Python thinks where the error has occurred. In Django, this may or may not be where the root cause of the bug is.
• Traceback: This was the call stack when the error occurred. The line that caused the error will be at the end. The nested calls that led to it will be above it. Don't forget to click on the 'Local vars' arrow to inspect the values of the variables at the time of the exception.
• Request information: This is a table (not shown in the screenshot) that shows context variables, meta information, and project settings. Check for malformed input in the requests here.

A better debug page

Often, you may wish for more interactivity in the default Django error page.
The django-extensions package ships with the fantastic Werkzeug debugger that provides exactly this feature. In the following screenshot of the same exception, notice a fully interactive Python interpreter available at each level of the call stack:

图片:略

To enable this, in addition to adding django_extensions to your INSTALLED_APPS, you will need to run your test server as follows:

 $ python manage.py runserver_plus

Despite the reduced debugging infor􏰀ation, I find the Werk􏰄eug debugger to be more useful than the default error page.

The print function

Sprinkling print() functions all over the code for debugging might sound primitive, but it has been the preferred technique for many programmers.

Typically, the print() functions are added before the line where the exception has occurred. It can be used to print the state of variables in various lines leading to the exception. You can trace the execution path by printing so􏰀ething when a certain line is reached.

In development, the print output usually appears in the console window where the test server is running. Whereas in production, these print outputs might end up in your server log file where they would add a runti􏰀e overhead.

In any case, it is not a good debugging technique to use in production. Even if you do, the print functions that are added for debugging should be removed from being committed to your source control.

Logging

The 􏰀ain reason for including the previous section was to say􏰩You should replace the print() functions with calls to logging functions in Python's logging module. Logging has several advantages over printing: it has a timestamp, a clearly marked level of urgency (for example, INFO, DEBUG), and you don't have to remove them from your code later.

Logging is fundamental to professional web development. Several applications in your production stack, like web servers and databases, already use logs. Debugging might take you to all these logs to retrace the events that lead to a bug. It is only appropriate that your application follows the same best practice and adopts logging for errors, warnings, and informational messages.

Unlike the common perception, using a logger does not involve too much work. Sure, the setup is slightly involved but it is merely a one-time effort for your entire project. Even more, most project templates (for example, the edge template) already do this for you.

􏰚nce you have configured the LOGGING variable in settings.py, adding a logger to your existing code is quite easy, as shown here:

# views.py
   import logging
   logger = logging.getLogger(__name__)
   def complicated_view():
       logger.debug("Entered the complicated_view()!")

The logging module provides various levels of logged messages so that you can easily filter out less urgent 􏰀essages. The log output can be also formatted in various ways and routed to 􏰀any places, such as standard output or log files. Read the documentation of Python's logging module to learn more.

The Django Debug Toolbar

The Django Debug Toolbar is an indispensable tool not just for debugging but also for tracking detailed information about each request and response. Rather than appearing only during exceptions, the toolbar is always present in your rendered page.

Initially, it appears as a clickable graphic on the right-hand side of your browser window. On clicking, a toolbar appears as a dark semi-transparent sidebar with several headers:

图片:略

􏰍ach header is filled with detailed infor􏰀ation about the page fro􏰀 the nu􏰀ber
of SQL queries executed to the templates that we use to render the page. Since the toolbar disappears when DEBUG is set to False, it is pretty much restricted to being a development tool.

The Python debugger pdb
While debugging, you might need to stop a Django application in the middle of execution to examine its state. A simple way to achieve this is to raise an exception with a simple assert False line in the required place.
What if you wanted to continue the execution step by step fro􏰀 that line? This is possible with the use of an interactive debugger such as Python's pdb. Simply insert the following line wherever you want the execution to stop and switch to pdb:

 import pdb; pdb.set_trace()

Once you enter pdb, you will see a command-line interface in your console window with a (Pdb) prompt. At the same time, your browser window will not display anything as the request has not finished processing.

The pdb command-line interface is extremely powerful. It allows you to go through the code line by line, examine the variables by printing them, or execute arbitrary code that can even change the running state. The interface is quite similar to GDB, the GNU debugger.

Other debuggers

There are several drop-in replacements for pdb. They usually have a better interface. Some of the console-based debuggers are as follows:

• ipdb: Like IPython, this has autocomplete, syntax-colored code, and so on.
• pudb: Like old Turbo C IDEs, this shows the code and variables side by side.
• IPython: This is not a debugger. You can get a full IPython shell anywhere in your code by adding the from IPython import embed; embed()line.

PuDB is my preferred replacement for pdb. It is so intuitive that even beginners can easily use this interface. Like pdb, just insert the following code to break the execution of the program:

import pudb; pudb.set_trace()

When this line is executed, a full-screen debugger is launched, as shown here:

图片:略

Press the ? key to get help on the complete list of keys that you can use.

Additionally, there are several graphical debuggers, some of which are standalone, such as winpdb and others, which are integrated to the IDE, such as PyCharm, PyDev, and Komodo. I would recommend that you try several of them until you find the one that suits your work􏰁ow.

Debugging Django templates

Projects can have very complicated logic in their templates. Subtle bugs while creating a te􏰀plate can lead to hard􏰃to􏰃find bugs. We need to set TEMPLATE_DEBUG to True (in addition to DEBUG) in settings.py so that Django shows a better error page when there is an error in your templates.

There are several crude ways to debug templates, such as inserting the variable of interest, such as {{ variable }}, or if you want to dump all the variables, use the built-in debug tag like this (inside a conveniently clickable text area):

<textarea onclick="this.focus();this.select()" style="width: 100%;">
     {% filter force_escape %}
{% debug %}
     {% endfilter %}
   </textarea>

A better option is use the Django Debug Toolbar mentioned earlier. It not only tells you the values of the context variables but also shows the inheritance tree of your templates.

However, you might want to pause in the middle of a template to inspect the state (say, inside a loop). A debugger would be perfect for such cases. In fact, it is possible to use any one of the aforementioned Python debuggers for your templates using custom template tags.

Here is a simple implementation of such a te􏰀plate tag. 􏰖reate the following file inside a templatetag package directory:

# templatetags/debug.py
   import pudb as dbg              # Change to any *db
   from django.template import Library, Noderegister = Library()
class PdbNode(Node):
    def render(self, context):
        dbg.set_trace()
return ''
@register.tag
def pdb(parser, token):
# Debugger will stop here
return PdbNode()

In your template, load the template tag library, insert the pdb tag wherever you need
the execution to pause, and enter the debugger:

{% load debug %}
   {% for item in items %}
       {# Some place you want to break #}
       {% pdb %}
{% endfor %}

Within the debugger, you can examine anything, including the context variables using the context dictionary:

>>> print(context["item"])
Item0

If you need more such template tags for debugging and introspection, then I would recommend that you check out the django-template-debug package.

Summary

In this chapter, we looked at the motivations and concepts behind testing in Django. We also found the various best practices to be followed while writing a test case.

In the section on debugging, we got familiar with the various debugging tools and techniques to find bugs in Django code and te􏰀plates.
In the next chapter, we will get one step closer to production code by understanding the various security issues and how to reduce threats from various kinds of malicious attacks.

第五章-模板

第五章 模板


本章,我们会讨论以下议题:

1. Django模板语法的特性
2. 组织模板
3. Bootstrap
4. 模板继承树模式
5. 活动连接模式

理解Django的模板语法特性

It is time to talk about the third musketeer in the MTV trio—templates. Your team might have designers who take care of designing templates. Or you might be designing them yourself. Either way, you need to be very familiar with them. They are, after all, directly facing your users.

Let's start with a quick primer of Django's template language features.
让我们从一份Django模板语法特性的快速入门开始吧。

变量

Each template gets a set of context variables. Similar to Python's string format() method's single curly brace {variable} syntax, Django uses the double curly brace {{ variable }} syntax. Let's see how they compare:

每个模板都获取到一组上下文变量。类似与Python的字符串format()方法的单一大括号{变量}语法,Django使用一对大括号{{变量}}语法。让我们看看它们之间的比较:

• In Pure Python the syntax is <h1>{title}</h1>. For example: >>> "<h1>{title}</h1>".format(title="SuperBook") '<h1>SuperBook</h1>'

• The syntax equivalent in a Django template is <h1>{{ title }}</h1>.

• Rendering with the same context will produce the same output as follows:  
       >>> from django.template import Template, Context
       >>> Template("<h1>{{ title }}</h1>").render(Context({"title":
       "SuperBook"}))
       '<h1>SuperBook</h1>'

属性

Dot is a multipurpose operator in Django templates. There are three different kinds of operations—attribute lookup, dictionary lookup, or list-index lookup (in that order).

• In Python, first, let's define the context variables and classes:
       >>> class DrOct:
               arms = 4
               def speak(self):
                   return "You have a train to catch."
       >>> mydict = {"key":"value"}
       >>> mylist = [10, 20, 30]
        Let's take a look at Python's syntax for the three kinds of lookups:
       >>> "Dr. Oct has {0} arms and says: {1}".format(DrOct().arms,
       DrOct().speak())
       'Dr. Oct has 4 arms and says: You have a train to catch.'
       >>> mydict["key"]
        'value'
       >>> mylist[1]
        20
• In Django's template equivalent, it is as follows:
   Dr. Oct has {{ s.arms }} arms and says: 
   {{ s.speak }}
   {{ mydict.key }}
   {{ mylist.1 }}

Notice how speak, a method that takes no arguments except self, is treated like an attribute here.

过滤器

Sometimes, variables need to be modified. Essentially, you would like to call functions on these variables. Instead of chaining function calls, such as var.method1(). method2(arg),Djangousesthepipesyntax{{ var|method1|method2:"arg" }}, which is similar to Unix filters. However, this syntax only works for built-in or custom-defined filters.

Another limitation is that filters cannot access the template context. It only works with the data passed into it and its arguments. Hence, it is primarily used to alter the variables in the template context.

• Run the following command in Python:
       >>> title="SuperBook"
       >>> title.upper()[:5]
        'SUPER'
• Its Django template equivalent:
   {{ title|upper|slice:':5' }}

标签

Programming languages can do more than just display variables. Django's template language has many familiar syntactic forms, such as if and for. They should be written in the tag syntax such as {% if %}. Several template-specific forms, such
as include and block are also written in the tag syntax.

• Run the following command in Python:  
       >>> if 1==1:
       ...     print(" Date is {0} ".format(time.strftime("%d-%m-%Y")))
        Date is 31-08-2014
• Its corresponding Django template form:
       {% if 1 == 1 %} Date is {% now 'd-m-Y' %} {% endif %}

哲学,不要发明一种新的编程语言

A common question among beginners is how to perform numeric computations such as finding percentages in templates. As a design philosophy, the template system does not intentionally allow the following:

• Assignment to variables
• Advanced logic

This decision was made to prevent you from adding business logic in templates. From our experience with PHP or ASP-like languages, mixing logic with presentation can be a maintenance nightmare. However, you can write custom template tags (which will be covered shortly) to perform any computation, especially if it is presentation-related.

Best Practice

Keep business logic out of your templates.

组织模板

The default project layout created by the startproject command does not define a location for your templates. This is very easy to fix. Create a directory named templates in your project's root directory. Add the TEMPLATE_DIRS variable in your settings.py:

    BASE_DIR = os.path.dirname(os.path.dirname(__file__))
   TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]

That's all. For example, you can add a template called about.html and refer to it in the urls.py file as follows:

    urlpatterns = patterns(
       '',
       url(r'^about/$', TemplateView.as_view(template_name='about.html'),
           name='about'),

Your templates can also reside within your apps. Creating a templates directory inside your app directory is ideal to store your app-specific templates.

Here are some good practices to organize your templates:

• Keep all app-specific templates inside the app's template directory within a separate directory, for       example, projroot/app/templates/app/template. html—notice how app appears twice in the path
• Use the .html extension for your templates
• Prefix an underscore for templates, which are snippets to be included,
for example, _navbar.html  

支持其他模板语言

From Django 1.8 onward, multiple template engines will be supported. There will be built-in support for the Django template language (the standard template language discussed earlier) and Jinja2. In many benchmarks, Jinja2 is quite faster than Django templates.

It is expected that there will be an additional TEMPLATES setting for specifying the template engine and all template-related settings. The TEMPLATE_DIRS setting will be soon deprecated.

Madame O

For the first time in weeks, Steve's office corner was bustling with frenetic activity. With more recruits, the now five-member team comprised of Brad, Evan, Jacob, Sue, and Steve. Like a superhero team, their abilities were deep and amazingly well-balanced.

Brad and Evan were the coding gurus. While Evan was obsessed over details, Brad was the big-picture guy. Jacob's talent in finding corner cases made him perfect for testing. Sue was in charge of marketing and design.

In fact, the entire design was supposed to be done by an avant-garde design agency. It took them a month to produce an abstract, vivid, color-splashed concept loved by the management. It took them another two weeks to produce an HTML-ready version from their Photoshop mockups. However, it was eventually discarded as it proved to be sluggish and awkward on mobile devices.

Disappointed by the failure of what was now widely dubbed as the "unicorn vomit" design, Steve felt stuck. Hart had phoned him quite concerned about the lack of any visible progress to show management. In a grim tone, he reminded Steve, "We have already eaten up the project's buffer time. We cannot afford any last-minute surprises."

It was then that Sue, who had been unusually quiet since she joined, mentioned that she had been working on a mockup using Twitter's Bootstrap. Sue was the growth hacker in the team—a keen coder and a creative marketer.

She admitted having just rudimentary HTML skills. However, her mockup was surprisingly thorough and looked familiar to users of other contemporary social networks. Most importantly, it was responsive and worked perfectly on every device from tablets to mobiles.

The management unanimously agreed on Sue's design, except for someone named Madame O. One Friday afternoon, she stormed into Sue's cabin and began questioning everything from the background color to the size of the mouse cursor. Sue tried to explain to her with surprising poise and calm.

An hour later, when Steve decided to intervene, Madame O was arguing why the profile pictures must be in a circle rather than square. "But a site-wide change like that will never get over in time," he said. Madame O shifted her gaze to him and gave him a sly smile. Suddenly, Steve felt a wave of happiness and hope surge within him. It felt immensely reliving and stimulating. He heard himself happily agreeing to all she wanted.

Later, Steve learnt that Madame Optimism was a minor mentalist who could influence prone minds. His team loved to bring up the latter fact on the slightest occasion.

使用Bootstrap

Hardly anyone starts an entire website from scratch these days. CSS frameworks such as Twitter's Bootstrap or Zurb's Foundation are easy starting points with grid systems, great typography, and preset styles. Most of them use responsive web design, making your site mobile friendly.

2015-05-28 16 19 45

A website using vanilla Bootstrap Version 3.0.2 built using the Edge project skeleton
We will be using Bootstrap, but the steps will be similar for other CSS frameworks. There are three ways to include Bootstrap in your website:

• Find a project skeleton: If you have not yet started your project, then finding a project skeleton that already has Bootstrap is a great option. A project skeleton such as edge (created by yours truly) can be used as the initial structure while running startproject as follows:
       $ django-admin.py startproject --template=https://github.com/
       arocks/edge/archive/master.zip --extension=py,md,html myproj

Alternatively, you can use one of the cookiecutter templates with support for Bootstrap.

    • Use a package: The easiest option if you have already started your project is to use a package, such as django-frontend-skeleton or django-bootstrap-toolkit.

    • Manually copy: None of the preceding options guarantees that their version of Bootstrap is the latest one. Bootstrap releases are so frequent that package authors have a hard time keeping their files up to date. So, if you would like to work with the latest version of Bootstrap, the best option is to download it from http://getbootstrap.com yourself. Be sure to read the release notes to check whether your templates need to be changed due to backward incompatibility.

Copy the dist directory that contains the css, js, and fonts directories into your project root under the static directory. Ensure that this path is set for STATICFILES_DIRS in your settings.py:

       STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
    Now you can include the Bootstrap assets in your templates, as follows:
       {% load staticfiles %}
         <head>
           <link href="{% static 'css/bootstrap.min.css' %}"
       rel="stylesheet">

怎么它们看上去都一个样啊!

Bootstrap might be a great way to get started quickly. However, sometimes, developers get lazy and do not bother to change the default look. This leaves a poor impression on your users who might find your site's appearance a little too familiar and uninteresting.

Bootstrap comes with plenty of options to improve its visual appeal. There is a file called variables.less that contains several variables from the primary brand color to the default font, as follows:

    @brand-primary: #428bca;
    @brand-success: #5cb85c;
    @brand-info: #5bc0de;
    @brand-warning: #f0ad4e;
    @brand-danger: #d9534f;

    @font-family-sans-serif:  "Helvetica Neue", Helvetica, Arial, sans-
    serif;
    @font-family-serif:
    @font-family-monospace:
    monospace;
    @font-family-base:
    Georgia, "Times New Roman", Times, serif;
    Menlo, Monaco, Consolas, "Courier New",
    @font-family-sans-serif;  

Bootstrap documentation explains how you can set up the build system (including the LESS compiler) to compile these files down to the style sheets. Or quite conveniently, you can visit the 'Customize' area of the Bootstrap site to generate your customized style sheet online.

Thanks to the huge community around Bootstrap, there are also several sites, such as bootswatch.com, which have themed style sheets, that are drop-in replacements for your bootstrap.min.css.

Another approach is to override the Bootstrap styles. This is recommended if you find upgrading your customized Bootstrap style sheet between Bootstrap versions to be quite tedious. In this approach, you can add your site-wide styles in a separate CSS (or LESS) file and include it after the standard Bootstrap style sheet. Thus, you can simply upgrade the Bootstrap file with minimal changes to your site-wide style sheet.

Last but not the least, you can make your CSS classes more meaningful by replacing structural names, such as 'row' or 'column-md-4', with 'wrapper' or 'sidebar'. You can do this with a few lines of LESS code, as follows:

   .wrapper {
     .make-row();
    }
    .sidebar {
     .make-md-column(4);
   }

This is possible due to a feature called mixins (sounds familiar?). With the Less source files, Bootstrap can be completely customized to your needs.

模板模式

Django's template language is quite simple. However, you can save a lot of time by following some elegant template design patterns. Let's take a look at some of them.

模式-模板继承树

Problem: Templates have lots of repeated content in several pages.

Solution: Use template inheritance wherever possible and include snippets elsewhere.

问题细节

Users expect pages of a website to follow a consistent structure. Certain interface elements, such as navigation menu, headers, and footers are seen in most web applications. However, it is cumbersome to repeat them in every template.

Most templating languages have an include mechanism. The contents of another file, possibly a template, can be included at the position where it is invoked. This can get tedious in a large project.

The sequence of the snippets to be included in every template would be mostly the same. The ordering is important and hard to check for mistakes. Ideally, we should be able to create a 'base' structure. New pages ought to extend this base to specify only the changes or make extensions to the base content.

方案详情

Django templates have a powerful extension mechanism. Similar to classes in programming, a template can be extended through inheritance. However, for that to work, the base itself must be structured into blocks as follows:

2015-05-28 16 22 19

The base.html template is, by convention, the base structure for the entire site. This template will usually be well-formed HTML (that is, with a preamble and matching closing tags) that has several placeholders marked with the {% block tags %} tag. For example, a minimal base.html file looks like the following:

    <html>
    <body>
    <h1>{% block heading %}Untitled{% endblock %}</h1>
    {% block content %}
    {% endblock %}
    </body>
    </html>

There are two blocks here, heading and content, that can be overridden. You can extend the base to create specific pages that can override these blocks. For example, here is an about page:

    {% extends "base.html" %}
    {% block content %}
    <p> This is a simple About page </p>
    {% endblock %}
    {% block heading %}About{% endblock %}

Notice that we do not have to repeat the structure. We can also mention the blocks in any order. The rendered result will have the right blocks in the right places as defined in base.html.

If the inheriting template does not override a block, then its parent's contents are used. In the preceding example, if the about template does not have a heading, then it will have the default heading of 'Untitled'.

The inheriting template can be further inherited forming an inheritance chain. This pattern can be used to create a common derived base for pages with a certain layout, for example, single-column layout. A common base template can also be created for a section of the site, for example, blog pages.

Usually, all inheritance chains can be traced back to a common root, base.html; hence, the pattern's name—Template inheritance tree. Of course, this need not be strictly followed. The error pages 404.html and 500.html are usually not inherited and stripped bare of most tags to prevent further errors.

模式-活动链接

Problem: The navigation bar is a common component in most pages. However, the active link needs to reflect the current page the user is on.

Solution: Conditionally, change the active link markup by setting context variables or based on the request path.

问题细节

The naïve way to implement the active link in a navigation bar is to manually set it in every page. However, this is neither DRY nor foolproof.

方案详情

There are several solutions to determine the active link. Excluding JavaScript-based approaches, they can be mainly grouped into template-only and custom
tag-based solutions.

临时模板方案

By mentioning an active_link variable while including the snippet of the navigation template, this solution is both simple and easy to implement.

In every template, you will need to include the following line (or inherit it):

    {% include "_navbar.html" with active_link='link2' %}

The _navbar.html file contains the navigation menu with a set of checks for the
active link variable:

    {# _navbar.html #}
   <ul class="nav nav-pills">
     <li{% if active_link == "link1" %} class="active"{% endif %}><a
   href="{% url 'link1' %}">Link 1</a></li>
     <li{% if active_link == "link2" %} class="active"{% endif %}><a
   href="{% url 'link2' %}">Link 2</a></li>
     <li{% if active_link == "link3" %} class="active"{% endif %}><a
   href="{% url 'link3' %}">Link 3</a></li>
   </ul>

自定义标签

Django templates offer a versatile set of built-in tags. It is quite easy to create your own custom tag. Since custom tags live inside an app, create a templatetags directory inside an app. This directory must be a package, so it should have an (empty)__init__.py file.

Next, write your custom template in an appropriately named Python file. For example, for this active link pattern, we can create a file called nav.py with the following contents:

    # app/templatetags/nav.py
    from django.core.urlresolvers import resolve
    from django.template import Library
    register = Library()
    @register.simple_tag
    def active_nav(request, url):
       url_name = resolve(request.path).url_name
       if url_name == url:
           return "active"
       return ""

This file defines a custom tag named active_nav. It retrieves the URL's path component from the request argument (say, /about/—see Chapter 4, Views and URLs, for a detailed explanation of the URL path). Then, the resolve() function is used to lookup the URL pattern's name (as defined in urls.py) from the path. Finally, it returns the string "active" only when the pattern's name matches the expected pattern name.

The syntax for calling this custom tag in a template is {% active_nav request 'pattern_name' %}. Notice that the request needs to be passed in every page this tag is used.

Including a variable in several views can get cumbersome. Instead, we add a built-in context processor to TEMPLATE_CONTEXT_PROCESSORS in settings.py so that the request will be present in a request variable across the site, as follows:

     # settings.py
    from django.conf import global_settings
    TEMPLATE_CONTEXT_PROCESSORS = \
       global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
           'django.core.context_processors.request',
        )

Now, all that remains is to use this custom tag in your template to set the active attribute:

    {# base.html #}
   {% load nav %}
   <ul class="nav nav-pills">
     <li class={% active_nav request 'active1' %}><a href="{% url
   'active1' %}">Active 1</a></li>
     <li class={% active_nav request 'active2' %}><a href="{% url
   'active2' %}">Active 2</a></li>
     <li class={% active_nav request 'active3' %}><a href="{% url
   'active3' %}">Active 3</a></li>
   </ul>

总结

In this chapter, we looked at the features of Django's template language. Since it
is easy to change the templating language in Django, many people might consider replacing it. However, it is important to learn the design philosophy of the built-in template language before we seek alternatives.

In the next chapter, we will look into one of the killer features of Django, that is, the admin interface, and how we can customize it.

第六章-Admin接口

第六章-Admin接口


In this chapter, we will discuss the following topics:

• Customizing admin
• Enhancing models for the admin
• Admin best practices
• Feature flags

Django's much discussed admin interface makes it stand apart from the competition. It is a built-in app that automatically generates a user interface to add and modify
a site's content. For many, the admin is Django's killer app, automating the boring task of creating admin interfaces for the models in your project.

Admin enables your team to add content and continue development at the same time. Once your models are ready and migrations have been applied, you just need to add a line or two to create its admin interface. Let's see how.

使用admin接口

In Django 1.7, the admin interface is enabled by default. After creating your project, you will be able to see a login page when you navigate to http://127.0.0.1:8000/ admin/.

If you enter the superuser credentials (or credentials of any staff user), you will be logged into the admin interface, as shown in the following screenshot:
However, your models will not be visible here, unless you define a corresponding ModelAdmin class. This is usually defined in your app's admin.py as follows:

from django.contrib import admin
from . import models
admin.site.register(models.SuperHero)

Here, the second argument to register, a ModelAdmin class, has been omitted. Hence, we will get a default admin interface for the Post model. Let's see how to create and customize this ModelAdmin class.

The Beacon

"Having coffee?" asked a voice from the corner of the pantry. Sue almost spilled her coffee. A tall man wearing a tight red and blue colored costume stood smiling with hands on his hips. The logo emblazoned on his chest said in large type—Captain Obvious.

"Oh, my god," said Sue as she wiped the coffee stain with a napkin. "Sorry, I think I scared you," said Captain Obvious "What is the emergency?"
"Isn't it obvious that she doesn't know?" said a calm feminine voice from above. Sue looked up to find a shadowy figure slowly descend from the open hall. Her face was partially obscured by her dark matted hair that had a few grey streaks. "Hi Hexa!" said the Captain "But then, what was the message on SuperBook about?"

Soon, they were all at Steve's office staring at his screen. "See, I told you there is no beacon on the front page," said Evan. "We are still developing that feature." "Wait," said Steve. "Let me login through a non-staff account."
In a few seconds, the page refreshed and an animated red beacon prominently appeared at the top. "That's the beacon I was talking about!" exclaimed Captain Obvious. "Hang on a minute," said Steve. He pulled up the source files for the new features deployed earlier that day. A glance at the beacon feature branch code made it clear what went wrong:

if switch_is_active(request, 'beacon') and not
request.user.is_staff():
       # Display the beacon

"Sorry everyone," said Steve. "There has been a logic error. Instead of turning this feature on only for staff, we inadvertently turned it on for everyone but staff. It is turned off now. Apologies for any confusion."

"So, there was no emergency?" said Captain with a disappointed look. Hexa put an arm on his shoulder and said "I am afraid not, Captain." Suddenly, there was a loud crash and everyone ran to the hallway. A man had apparently landed in the office through one of the floor-to-ceiling glass walls. Shaking off shards of broken glass, he stood up. "Sorry, I came as fast as I could," he said, "Am I late to the party?" Hexa laughed. "No, Blitz. Been waiting for you to join," she said.

Enhancing models for the admin

The admin app is clever enough to figure out a lot of things from your model automatically. However, sometimes the inferred information can be improved. This usually involves adding an attribute or a method to the model itself (rather than at the ModelAdmin class).

Let's first take a look at an example that enhances the model for better presentation, including the admin interface:

# models.py
class SuperHero(models.Model):
   name = models.CharField(max_length=100)
   added_on = models.DateTimeField(auto_now_add=True)
   def __str__(self):
       return "{0} - {1:%Y-%m-%d %H:%M:%S}".format(self.name,
                                                   self.added_on)
   def get_absolute_url(self):
       return reverse('superhero.views.details', args=[self.id])
   class Meta:
       ordering = ["-added_on"]
       verbose_name = "superhero"
       verbose_name_plural = "superheroes"

Let's take a look at how admin uses all these non-field attributes:

• __str__(): Without this, the list of superhero entries would look extremely boring. Every entry would be plainly shown as <SuperHero: SuperHero object>. Try to include the object's unique information in its str representation (or unicode representation, in the case of Python 2.x code), such as its name or version. Anything that helps the admin to recognize the object unambiguously would help.

• get_absolute_url(): This attribute is handy if you like to switch between the admin view and the object's detail view on your website. If this method is defined, then a button labelled "View on site" will appear in the top right-hand side of the object's edit page in its admin page.

• ordering: Without this meta option, your entries can appear in any order as returned from the database. As you can imagine, this is no fun for the admins if you have a large number of objects. Fresh entries are usually preferred to be seen first, so sorting by date in the reverse chronological order is common.

• verbose_name: If you omit this attribute, your model's name would be converted from CamelCase into camel case. In this case, "super hero" would look awkward, so it is better to be explicit about how you would like the user-readable name to appear in the admin interface.

• verbose_name_plural: Again, omitting this option can leave you with funny results. Since Django simply prepends an 's' to the word, the plural of a superhero would be shown as "superheros" (on the admin front page, no less). So, it is better to define it correctly here.

It is recommended that you define the previous Meta attributes and methods, not just for the admin interface, but also for better representation in the shell, log files, and so on.

Of course, a further improved representation within the admin is possible by creating a ModelAdmin class as follows:

 # admin.py
class SuperHeroAdmin(admin.ModelAdmin):
   list_display = ('name', 'added_on')
   search_fields = ["name"]
   ordering = ["name"]
admin.site.register(models.SuperHero, SuperHeroAdmin)

Let's take a look at these options more closely:

• list_display: This option shows the model instances in a tabular form. Instead of using the model's __str__ representation, it shows each field mentioned as a separate sortable column. This is ideal if you like to see more than one attribute of your model.  

• search_fields: This option shows a search box above the list. Any search term entered would be searched against the mentioned fields. Hence, only text fields such as CharField or TextField can be mentioned here.  

• ordering: This option takes precedence over your model's default ordering. It is useful if you prefer a different ordering in your admin screen.

图片:略

The preceding screenshot shows the following insets:
• Inset 1: Without str or Meta attributes
• Inset 2: With enhanced model meta attributes
• Inset 3: With customized ModelAdmin

Here, we have only mentioned a subset of commonly used admin options. Certain kinds of sites use the admin interface heavily. In such cases, it is highly recommended that you go through and understand the admin part of the Django documentation.

不应该让所有人都成为admin

Since admin interfaces are so easy to create, people tend to misuse them. Some give early users admin access by merely turning on their 'staff' flag. Soon such users begin making feature requests, mistaking the admin interface to be the actual application interface.

Unfortunately, this is not what the admin interface is for. As the flag suggests, it is an internal tool for the staff to enter content. It is production-ready but not really intended for the end users of your website.
It is best to use admin for simple data entry. For example, in a project I had reviewed, every teacher was made an admin for a Django application managing university courses. This was a poor decision since the admin interface confused the teachers.

The workflow for scheduling a class involves checking the schedules of other teachers and students. Using the admin interface gives them a direct view of the database. There is very little control over how the data gets modified by the admin.

So, keep the set of people with admin access as small as possible. Make changes via admin sparingly, unless it is simple data entry such as adding an article's content.

Best Practice

Don't give admin access to end users.

Ensure that all your admins understand the data inconsistencies that can arise from making changes through the admin. If possible, record manually or use apps, such as django-audit-loglog that can keep a log of admin changes made for future reference.

In the case of the university example, we created a separate interface for teachers, such as a course builder. These tools will be visible and accessible only if the user has a teacher profile.

Essentially, rectifying most misuses of the admin interface involves creating more powerful tools for certain sets of users. However, don't take the easy (and wrong) path of granting them admin access.

Admin interface customizations

The out-of-box admin interface is quite useful to get started. Unfortunately, most people assume that it is quite hard to change the Django admin and leave it as it is. In fact, the admin is extremely customizable and its appearance can be drastically changed with minimal effort.

Changing the heading

Many users of the admin interface might be stumped by the heading—Django administration. It might be more helpful to change this to something customized such as MySite admin or something cool such as SuperBook Secret Area.

It is quite easy to make this change. Simply add this line to your site's urls.py:
admin.site.site_header = "SuperBook Secret Area"

Changing the base and stylesheets

Almost every admin page is extended from a common base template named admin/base_site.html. This means that with a little knowledge of HTML and CSS, you can make all sorts of customizations to change the look and feel of the admin interface.

Simply create a directory called admin in any templates directory. Then, copy the base_site.html file from the Django source directory and alter it according to your needs. If you don't know where the templates are located, just run the following commands within the Django shell:

>>> from os.path import join
>>> from django.contrib import admin
>>> print(join(admin.__path__[0], "templates", "admin"))

For an example of customizing the admin base template, you can change the font of the entire admin interface to "Special Elite" from Google Fonts, which is great for giving a mock-serious look. You will need to add an admin/base_site.html file in one of your template's directories with the following contents:

{% extends "admin/base.html" %}
{% block extrastyle %}
   <link href='http://fonts.googleapis.com/css?family=Special+Elite'
rel='stylesheet' type='text/css'>
   <style type="text/css">
    body, td, th, input {
      font-family: 'Special Elite', cursive;
} </style>
{% endblock %}

This adds an extra stylesheet for overriding the font-related styles and will be applied to every admin page.

Adding a Rich Text Editor for WYSIWYG editing

Sometimes, you will need to include JavaScript code in the admin interface. A common requirement is to use an HTML editor such as CKEditor for your TextField.

There are several ways to implement this in Django, for example, using a Media inner class on your ModelAdmin class. However, I find extending the admin change_form template to be the most convenient approach.

For example, if you have an app called Posts, then you will need to create a file called change_form.html within the templates/admin/posts/ directory. If you need to show CKEditor (could be any JavaScript editor for that matter, but this one is the one I prefer) for the message field of any model in this app, then the contents of the file can be as follows:

/home/arun/env/sbenv/lib/python3.4/site-packages/django/contrib/admin/
templates/admin  

The last line is the location of all your admin templates. You can override or extend any of these templates. Please refer to the next section for an example of extending the template.

{% extends "admin/change_form.html" %}
{% block footer %}
 {{ block.super }}
 <script src="//cdn.ckeditor.com/4.4.4/standard/ckeditor.js"></
script>
<script> CKEDITOR.replace("id_message", {
    toolbar: [
    [ 'Bold', 'Italic', '-', 'NumberedList', 'BulletedList'],],
    width: 600,
  });
 </script>
 <style type="text/css">
  .cke { clear: both; }
 </style>
{% endblock %}

The highlighted part is the automatically created ID for the form element we wish to enhance from a normal textbox to a Rich Text Editor. These scripts and styles have been added to the footer block so that the form elements would be created in the DOM before they are changed.

Bootstrap-themed admin

Overall, the admin interface is quite well designed. However, it was designed in 2006 and, for the most part, looks that way too. It doesn't have a mobile UI or other niceties that have become standard today.

Unsurprisingly, the most common request for admin customization is whether it can be integrated with Bootstrap. There are several packages that can do this, such as django-admin-bootstrapped or djangosuit.

Rather than overriding all the admin templates yourself, these packages provide ready-to-use Bootstrap-themed templates. They are easy to install and deploy. Being based on Bootstrap, they are responsive and come with a variety of widgets and components.

Complete overhauls

There have been attempts made to completely reimagine the admin interface too. Grappelli is a very popular skin that extends the Django admin with new features, such as autocomplete lookups and collapsible inlines. With django-admin-tools, you get a customizable dashboard and menu bar.

There have been attempts made to completely rewrite the admin, such as django-admin2 and nexus, which did not gain any significant adoption. There is even an official proposal called AdminNext to revamp the entire admin app. Considering the size, complexity, and popularity of the existing admin, any such effort is expected to take a significant amount of time.

Protecting the admin

The admin interface of your site gives access to almost every piece of data stored. So, don't leave the metaphorical gate lightly guarded. In fact, one of the only telltale signs that someone runs Django is that, when you navigate to http://example. com/admin/, you will be greeted by the blue login screen.

In production, it is recommended that you change this location to something less obvious. It is as simple as changing this line in your root urls.py:

url(r'^secretarea/', include(admin.site.urls)),

A slightly more sophisticated approach is to use a dummy admin site at the default location or a honeypot (see the django-admin-honeypot package). However, the best option is to use HTTPS for your admin area since normal HTTP will send all the data in plaintext over the network.

Check your web server documentation on how to set up HTTPS for admin requests. On Nginx, it is quite easy to set this up and involves specifying the SSL certificate locations. Finally, redirect all HTTP requests for admin pages to HTTPS, and you can sleep more peacefully.

The following pattern is not strictly limited to the admin interface but it is nonetheless included in this chapter, as it is often controlled in the admin.

Pattern – feature flags

Problem: Publishing of new features to users and deployment of the corresponding code in production should be independent.

Solution: Use feature flags to selectively enable or disable features after deployment.

Problem details

Rolling out frequent bug fixes and new features to production is common today. Many of these changes are unnoticed by users. However, new features that have significant impact in terms of usability or performance ought to be rolled out in a phased manner. In other words, deployment should be decoupled from a release.

Simplistic release processes activate new features as soon as they are deployed. This can potentially have catastrophic results ranging from user issues (swamping your support resources) to performance issues (causing downtime).

Hence, in large sites it is important to decouple deployment of new features in production and activate them. Even if they are activated, they are sometimes seen only by a select group of users. This select group can be staff or a sample set of customers for trial purposes.

Solution details

Many sites control the activation of new features using Feature Flags. A feature flag is a switch in your code that determines whether a feature should be made available to certain customers.

Several Django packages provide feature flags such as gargoyle and django-waffle. These packages store feature flags of a site in the database. They can be activated or deactivated through the admin interface or through management commands. Hence, every environment (production, testing, development, and so on) can have its own set of activated features.

Feature flags were originally documented, as used in Flickr (See http://code. flickr.net/2009/12/02/flipping-out/). They managed a code repository without any branches, that is, everything was checked into the mainline. They
also deployed this code into production several times a day. If they found out
that a new feature broke anything in production or increased load on the database, then they simply disabled it by turning that feature flag off.

Feature flags can be used for various other situations (the following examples use django-waffle):

• Trials:
    A feature flag can also be conditionally active for certain users.
    These can be your own staff or certain early adopters than you may be targeting as follows:
       def my_view(request):
           if flag_is_active(request, 'flag_name'):
               # Behavior if flag is active.

    Sites can run several such trials in parallel, so different sets of users might actually have different user experiences. Metrics and feedback are collected from such controlled tests before wider deployment.

• A/B testing: This is quite similar to trials except that users are selected randomly within a controlled experiment. This is quite common in web design to identify which changes can increase the conversion rates. This is how such a view can be written:

       def my_view(request):
           if sample_is_active(request, 'design_name'):
               # Behavior for test sample.

• Performance testing: Sometimes, it is hard to measure the impact of a feature on server performance. In such cases, it is best to activate the flag only for a small percentage of users first. The percentage of activations can be gradually increased if the performance is within the expected limits.

• Limit externalities: We can also use feature flags as a site-wide feature switch that reflects the availability of its services. For example, downtime in external services such as Amazon S3 can result in users facing error messages while they perform actions, such as uploading photos.

When the external service is down for extended periods, a feature flag can be deactivated that would disable the upload button and/or show a more helpful message about the downtime. This simple feature saves the user's time and provides a better user experience:

       def my_view(request):
           if switch_is_active('s3_down'):
               # Disable uploads and show it is downtime

The main disadvantage of this approach is that the code gets littered with conditional checks. However, this can be controlled by periodic code cleanups that remove checks for fully accepted features and prune out permanently deactivated features.

总结

In this chapter, we explored Django's built-in admin app. We found that it is not only quite useful out of the box, but that various customizations can also be done to improve its appearance and functionality.

In the next chapter, we will take a look at how to use forms more effectively in Django by considering various patterns and common use cases.

第三章-模型

第三章 模型

本章,我们将讨论以下话题:

模型的重要性  
类图表  
模型结构模式  
模型行为模式  
迁移  

M比V和C都更大

在Django中,模型是具有处理数据库的一种面向对象的方法的类。通常,每个类都引用一个数据库表,,每个属性都引用一个数据库列。你可以使用自动生成的API查询这些表。

模型是很多其他组件的基础。只要你有一个模型,你可以快速地推导模型admin,模型表单,以及所有类型的通用视图。在每个例子中,你都需要下一个行或是两行代码,这样可以让它看上去没有太多魔法。

模型也被用在更多超出你期望的地方。这书因为Django可以以多种方法运行。Django的一些切入点如下:

熟悉web请求-响应流程
Django的交互式shell
管理命令
测试脚本
异步任务队列比如Celery

在多数的这些例子中,模型模块要导入(作为django.setup()的一部分)。因此,最好保证模型远离任何不必要的依赖,或者导入任何的其他Django组件,比如视图。

简而言之,恰当地设计模型是很重要的事情。现在,让我们从SuperBook模型设计开始。

注释

自带午餐便当
*作者注释:SuperBook项目的进度会以这样的一个盒子出现。你可以跳过这个盒子,但是在web应用项目中的情况下,你缺少的是领悟,经验 。

史蒂夫和客户的第一周——超级英雄情报监控(简称为S.H.I.M)。简单来说,这是一个大杂烩。办公室是非常未来化的,但是不论做什么事情都需要上百个审核和签字。

作为Django开发者的领队,史蒂夫已经配置好了中型的运行超过两天的4台虚拟机。第二天的一个早晨,机器自己不翼而飞了。一个附近的清洁机器人说,机器被法务部们给带走了,他们要对未经审核的软件安装做出处理。

然而,CTO哈特给予史蒂夫了极大的帮助。他要求机器在一个小时之内完好无损地给还回去。他还对SuperBook项目做出了提前审核以避免将来可能出现的任何阻碍。

那个下午的稍晚些时候,史蒂夫给他带了一个午餐便当。身着一件米色外套和浅蓝色牛仔裤的哈特如约而至。尽管高出周围人许多,有着清爽面庞的他依旧那么帅气,那么平易近人。他问史蒂夫如果他之前是否尝试过构建一个60年代的超级英雄数据库。

”嗯,对的,是哨兵项目么?“,史蒂夫说道。”是我设计的。数据库看上去被设计成了一个条目-属性-值形式的模式,有些地方我考虑用反模式。可能,这些天他们有一些超级英雄属性的小想法。哈特几乎等不到听完最后一句,他压低嗓门道:“没错。是我的错。另外,他们只给了我两天来设计整个架构。他们这是在要我老命啊!”

听了这些,史蒂夫的嘴巴张的大大的,三明治也卡在口中。哈特微笑着道:“当然了,我还没有尽全力来做这件事。只要它成长为100万美元的单子,我们就可以多花点时间在这该死的数据库上了。SuperBook用它就能分分钟完事的,小史你说呢?”

史蒂夫微微点头称是。他从来没有想过在这么样的地方将会有上百万的超级英雄出现。

模型搜寻

这是在SuperBook中确定模型的第一部分。通常对于一个早期试验,我们只表示了基本模型,以及在一个类表中的表单的基本关系:

2015-05-28 14 53 47

让我们忘掉模型一会儿,来谈谈我们正在构建的对象的术语。每个用户都有一个账户。用户可以写多个回复或者多篇文章。Like可以同时关联到一个单独的用户或者文章。

推荐你像这样画一个模型的类表。这个步骤的某些属性或许缺失了,不过你可以在之后补充细节。只要整个项目用图表表示出来,便可以轻松地分离app。

这是创建此表示的一些提示:

盒子表示条目,它将成为模型。
名词通常作为条目的终止。
箭头是双向的,表示Django中三种关系类型其中的一种:一对一,一对多(通过外键实现),和多对多。
字段表明在基于条目-关系模型(ER-modle)的模型中定义了一对多关系。换句来说,星号是声明外键的地方。  

类图表可以映射到下面的Django代码中(它将遍及多个app):

    class Profile(models.Model):
        user = models.OnToOneField(User)

    class Post(models.Model):
        posted_by = models.ForeignKey(User)

    class Comment(models.Model):
        commented_by = models.ForeignKey(User)
        for_post = models.ForeignKey(Post)

    class Like(models.Model):
        liked_by = models.ForeignKey(User)
        post = models.ForeignKey(Post)

这之后,我们不会直接地引用_User_,而是使用更加普通的_settings.AUTH_USER_MODEL_来替代。

分割model.py到多个文件

就像多数的Django组件那样,一个大的model.py文件可以在一个包内分割为多个文件。package通过一个目录来实现,它包含多个文件,目录中的一个文件必须是一个称为__init__.py特殊文件。

所有可以在包级别中暴露的定义都必须在__init__.py里使用全局变量域定义。例如,如果我们分割model.py到独立的类,models子文件夹中的对应文件,比如,postable.py,post.py和comment.py, 之后__init__.py包会像这样:

    from postable import Postable
    from post import Post
    from commnet import Comment

现在你可以像之前那样导入models.Post了。

__init__.py包中的任何其他代码都会在包运行时被导入。因此,它是一个任意级别包初始化代码的理想之地。

结构模式

本节包含多个帮助你设计和构建模型的设计模式。

模式-规范化模型

问题:通过设计,模型实例的重复数据引起数据不一致。

解决方法:通过规范化,分解模型到更小的模型。使用这些模型之间的逻辑关系来连接他们。

问题细节

想象一下,如果某人用下面的方法设计Post表(省略部分列):

超级英雄的名字 消息 发布时间
Captain Temper 消息已经发布过了? 2012/07/07/07:15
Professor English 应该用“Is”而不是“Has" 2012/07/07/07:17
Captain Temper 消息已经发布过了? 2012/07/07/07:18
Capt. Temper 消息已经发布过了? 2012/07/07/07:19

我希望你注意到了在最后一行的超级英雄名字和之前不一致(船长一如既往的缺乏耐心)。

如果我们看看第一列,我们也不确定哪一个拼写是正确的——Captain Temper或者Capt.Temper。这就是我们要通过规范化消除的一种数据冗余。

详解

在我们看下完整的规范方案,让我们用Django模型的上下文来个关于数据库规范化的简要说明。

规范化的三个步骤

规范化有助于你更有效地的存储数据库。只要模型完全地的规范化处理,他们就不会有冗余的数据,每个模型应该只包含逻辑上关联到自身的数据。

这里给出一个简单的例子,如果我们规范化了Post表,我们就可以不模棱两可地引用发布消息的超级英雄,然后我们需要用一个独立的表来隔离用户细节。默认,Django已经创建了用户表。因此,你只需要在第一列中引用发布消息的用户的ID,一如下表所示:

用户ID 消息 发布时间
12 消息已经发布过了? 2012/07/07/07:15
8 应该用“Is”而不是“Has" 2012/07/07/07:17
12 消息已经发布过了? 2012/07/07/07:18
12 消息已经发布过了? 2012/07/07/07:19

现在,不仅仅相同用户发布三条消息的清楚在列,而且我们可以通过查询用户表找到用户的正确的名字。

通常来说,你会按照模型的完全规规范化表来设计模型,也会因为性能原因而有选择性地非规范化设计。在数据库中,Normal Forms是一组可以被应用于表,确保表被规范化的指南。一般我们建立第一,第二,第三规范表,尽管他们可以递增至第五规范表。

这接下来的例子中,我们规范化一个表,创建对应的Django模型。想象下有个名字称做“Sightings”的表格,它列出了某人第一次发现超级英雄使用能力或者特异功能。每个条目都提到了已知的原始身份,超能力,和第一次发现的地点,包括维度和经度。

名字 原始信息 能力 第一次使用记录(维度,经度,国家,时间)
Blitz Alien Freeze Flight +40.75, -73.99; USA; 2014/07/03 23:12
Hexa Scientist Telekinesis Flight +35.68, +139.73; Japan; 2010/02/17 20:15
Traveller Billonaire Time travel +43.62, +1.45, France; 2010/11/10 08:20

上面的地理数据提取自http://www.golombek.com/locations.html.

第一规范表(1NF)

为了确认第一个规范表格,这张表必须含有:

多个没有属性(cell)的值
一个主键作为单独一列或者一组列(合成键)

让我们试着把表格转换为一个数据库表。明显地,我们的Power列破坏了第一个规则。

更新过的表满足第一规范表。主键(用一个*标记)是NamePower的合并,对于每一排它都应该是唯一的。

Name* Origin Power* Latitude Longtitude Country Time
Blitz Alien Freeze +40.75170 -73.99420 USA 2014/07/03 23:12
Blitz Alien Flight +40.75170 -73.99420 USA 2013/03/12 11:30
Hexa Scientist Telekinesis +35.68330 +139.73330 Japan 2010/02/17 20:15
Hexa Scientist Filght +35.68330 +139.73330 Japan 2010/02/19 20:30
Traveller Billionaire Time tavel +43.61670 +1.45000 France 2010/11/10 08:20

第二规范表

第二规范表必须满足所有第一规范表的条件。此外,它必须满足所有非主键列都必须依赖于整个主键的条件。

在之前的表,我们注意到Origin只依赖于超级英雄,即,Name。不论我们谈论的是哪一个Power。因此,Origin不是完全地依赖于合成组件-NamePower

这里,让我们只取出原始信息到一个独立的,称做Origins的表:

Name* Origin
Blitz Alien
Hexa Scientist
Traveller Billionaire

现在Sightings表更新为兼容第二规范表,它大概是这个样子:

Name* Power* Latitude Longtitude Country Time
Blitz Freeze +40.75170 -73.99420 USA 2014/07/03 23:12
Blitz Flight +40.75170 -73.99420 USA
Hexa Telekinesis +35.68330 +139.73330 Japan 2010/02/17 20:15
Hexa Filght +35.68330 +139.73330 Japan 2010/02/19 20:30
Traveller Time tavel +43.61670 +1.45000 France 2010/11/10 08:20

第三规范表

在第三规范表中,比表格必须满足第二规范表,而且应该额外满足所有的非主键列都直接依赖整个主键,而且这些非主键列都是互相独立的这个条件。

考虑下Country类。给出维度经度,你可以轻松地得出Country列。即使观测到超级英雄的地方依赖于Name-Power合成键,但是它只是间接地依赖他们。

因此,我们把详细地址分离到一个独立的国家表格中:

Location ID Latitude* Longtitude* Country
1 +40.75170 -73.99420 USA
2 +35.68330 +139.73330 Japan
3 +43.61670 +1.45000 France

现在Sightings表格的第三规范表大抵如此:

User ID* Power* Location ID Time
2 Freeze 1 2014/0703 23:12
2 Flight 1 2013/03/12 11:30
4 Telekinesis 2 2010/02/17 20:15
4 Flight 2 2010/02/19 20:30
7 Time tavel 3 2010/11/10 08:20

如之前所做的那样,我们用对应的User ID替换了超级英雄的名字,这个用户ID用来引用用户表格。

Django模型

现在我们可以看看这些规范化的表格可以用来表现Django模型。Django中并不直接支持合成键。这里用到的解决方案是应用代理键,以及在Meta类中指定unique_together属性:

    class Origin(models.Model):
        superhero = models.ForeignKey(settings.AUTH_USER_MODEL)
        origin = models.CharField(max_length=100)

    class Location(models.Model):
        latitude = models.FloatField()
        longtitude = models.FloatField()
        country = models.CharField(max_length=100)

        class Meta:
            unique_together = ("latitude", "longtitude")

    class Sighting(models.Model):
        superhero = models.ForeignKey(settings.AUTH_USER_MODEL)
        power = models.CharField(max_length=100)
        location = models.ForeignKey(Location)
        sighted_on = models.DateTimeField()

        class Meta:
            unique_together = ("superhero", "power")

性能和非规范化

规范化可能对性能有不利的影响。随着模型的增长,需要应答查询的连接数也随之增加。例如,要在美国发现具有冷冻能力的超级英雄的数量,你需要连接四个表格。先前的内容规范后,任何信息都可以通过查询一个单独的表格被找到。

你应该设计模式以保持数据规范化。这可以维持数据的完整。然而,如果你面临扩展性问题,你可以有选择性地从这些模型取得数据以生成非规范化的数据。

提示

最佳实践
因设计而规范,又因优化而非规范
例如,在一个确定的国家中计算观测次数是非常普通的,然后将观测次数作为一个附加的字段到Location模型。现在,你可以使用Django ORM 继承其他的查询,而不是一个缓存的值。

然而,你需要在每次添加或者移除观测时更新这个计数。你需要添加该计算到_Singhting_的save方法,添加一个信号处理器,甚至使用一个异步任务去计算。

如果你有一个跨越多个表的负责查询,比如国家的超能力计算,你需要创建一个独立的非规范表格。就像前面那样,我们需要在每一次规范化模型中的数据改变时更新这个非规范的表格。

令人惊讶的是非规范化在大型的网站中是非常普遍的,因为它是数度和存储空间两者之间的折衷。今天的存储空间已经比较便宜了,然而速度也是用户体验中至关重要的一环。因此,如果你的查询耗时过于久的话,那么就需要考虑非规范化了。

我们应该一直使用规范化吗?

过多的规范化是是件不必要的事。有时候,它可以引入一个非必需的能够重复更新和查询的表格。

例如,你的User模型或许有好多个家庭地址的字段,你可以规范这些字段到一个Address模型中。可是,多数情况下,把一个额外的表引进数据库是没有必要的。

与其针对大多数的非规范化设计,不如在代码重构之前仔细地衡量每个非规范化的机会,对性能和速度上做出一个折衷的选择。

模式-模型mixins

问题:明显地模型含有重复的相同字段/或者方法,违反了DRY原则。

方案:提取公共字段和方法到各种不同的可重复使用的模型mixins中。

问题细节

设计模型之时,你或许某些个公共属性或者行为跨类共享。烈日,PostComment模型需要一直跟踪自己的created日期和modified日期。手动地复制-粘贴字段和它们所关联的方法并不符合DRY原则。

由于Django的模型是类,像合成以及继承这样的面向对象方法都是可以选择的解决方案。然而,合成(具有包含一个共享类实例的属性)需要一个额外的间接地访问字段的标准。

继承是有技巧的。我们使用一个PostComment的公共基类。然而,在Django中有三种类型的继承:concrete(具体), abstract(抽象), 和proxy(代理)

具体继承的运行是源于基类,就像你在Python类中通常用到的那样。不过,在Django中,这个基类将被映射到一个独立的表中。每次你访问基本字段时,都需要一个准确的连接。这会带来非常糟糕的性能问题。

代理继承只能添加新的行为到父类。你不能够添加新字段。因此,这种情况下它也不大好用。

最后,我们只有托付于抽象继承了。

详解

抽象基类是用于模型之间共享数据和行为的游戏简洁方案。当你定义一个基类时,它在数据中没有创建任何与之对象的表。反而,这些字段是在派生的非基类中创建的。

访问抽象基类字段不要JOIN语句。有支配的字段结构表也是不解自明的。对于这些优势,大多数的Django项目都使用抽象基类实现公共字段或者方法。

使用抽象模型是局限的:

它们不能够拥有外键或者来自其他模型的多対多字段。
它们不能够被实例化或者保存。
它们不能够直接用在查询中,因为它没有管理器。  

下面是post和comment类如何使用一个抽象基类初始设计的:

    class Postable(models.Model):
        created = models.DateTimeField(auto_now_add=True)
        modified = modified.DateTimeField(auto_now=True)
        message = models.TextField(max_length=500)

        class Meta:
            abstract = True


    class Post(Postable):
        ...


    class Comment(Postable):
        ...

要将一个模型转换到抽象基类,你需要在它的内部Meta类中写上abstract = True。这里的Postable是一个抽象基类。可是,它不是那么的可复用。

实际上,如果有一个类含有createdmodified字段,我们在后面就可以在附近的任何需要时间戳的模型中重复使用这个时间戳功能。

模型mixins

模型mixins是一个可以把抽象基类当作父类来添加的模型。不像其他的语法,比如Java那样,Python支持多种继承。因此,你可以列出一个模型的任意数量的父类。

Mixins应该是互相垂直的而且易于组合的。把一个mixin放进基类的列表,这些mixin应该可以正常运行。这样看来,它们在行为上更类似于合成而非继承。

较小的mixin的会更好。不论何时一个mixin变得庞大,而且又违反了独立响应原则,就要考虑把它重构到一个更小的类。就让一个mixin一次做好一件吧。

在前面的例子中,模型mixin用于更新createdmodified的时间可以轻松地分解出来,一如下面代码所示:

    class TimeStampedModel(models.Model):
        created = modified.TimeStampModel(auto_now_add=True)
        modified = modified.DateTimeField(auto_now=True)

        class Meta:
            abstract = True


    class Postable(TimeStampedModel):
        message = models.TextField(max_length=500)
        ...

        class Meta:
            abstract = True


    class Post(Postable):
        ...


    class Comment(Postable):
        ...

我们现在有两个超类了。不过,功能之间都完全地独立。mixin可以分离到自己的模块之内,并且另外的上下文中被复用。

模式-用户账户

问题:每一个网站都存储一组不同的用户账户细节。然而,Django的内建User模型旨在针对认证细节。

方案:用一个一对一关系的用户模型,创建一个用户账户类。

问题细节

Django提供一个开箱即用的相当不错的User模型。你可以在创建超级用户或者登录amdin接口的时候用到它。它含有少量的基本字段,比如全名,用户名,和电子邮件。

然而,大多数的现实世界项目都保留了很多关于用户的信息,比如他们的地址,喜欢的电影,或者它们的超能力。打Django1.5开始,默认的User模型就可以被扩展或者替换掉。不过,官方文档极力推荐只存储认证数据,即便是在定制的用户模型中也是如此(毕竟,用户模型也是所属于auth这个app的)。

某些项目是需要多种类型的用户的。例如,SuperBook可以被超级英雄和非超级英雄所使用。这里或许会有一些公共字段,以及基于用户类型的不同字段。

详解

官方推荐解决方案是创建一个用户账户模型。它应该和用户模型有一个一对一的关系。其余的全部用户信息都存储于该模型:

    class Profile(models.Model):
        user = models.OnToOneField(settings.AUTH_USER_MODEL, primary_key=True)

这里推荐你明确的将primary_key赋值为True以阻止类似PostgreSQL这样的数据库后端中的并发问题。剩下的模型可以包含其他的任何用户详情,比如生日,喜好色彩,等等。

设计账户模型之时,建议所有的账户详情字段都必须是非空的,或者含有一个默认值。凭直觉我们就知道用户在注册时是不可能填写完所有的账户细节的。此外,我们也要确保创建账户实例时,信号处理器没有传递任何初始参数。

信号

理论上,每一次用户模型的创建都必须把对应的用户账户实例创建好。这个操作通常使用信号来完成。例如,我们可以使用下面的信号处理器侦听用户模型的post_save信号:

    # signals.py
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from django.conf import settings
    from . import models


    @receiver(post_save, sender=settings.AUTH_USER_MODEL)
    def create_profile_handler(sender, instance, created, **kwargs):
        if not created:
            return
        # Create the profile object, only if it is newly created
        profile = models.Profile(user=instance)
        profile.save()

注意账户模型除了用户实例之外没有传递额外的参数。

之前没有指定初始信号代码的地方。通常,它们在models.py中(这是不可靠的)导入或者执行。不过,随着Django 1.7的app载入重构,应用初始化代码位置的问题也很好的解决了。

首先,为你的应用创建一个__init__.py包以引用应用的ProfileConfig:

default_app_config = "profile.apps.ProfileConfig"

接下来是app.py中,子类ProfileConfig的方法,可于ready方法中配置信号:

    # app.py
    from django.apps import AppConfig


    class ProfileConfig(AppConfig):
        name = "profiles"
        verbose_name = "User Profiles"


        def ready(self):
            from . import signals

随着信号的配置,对所有的用户来说,访问user.profile应该都返回一个Profile对象,即使是最新创建的用户也是如此。

Admin

现在,用户的详情为存在admin内的两个不同地方:普通用户admin页面中的认证细节,在一个独立账户admin页面中的相同用户的补充账户详情。但是这样做非常麻烦。

为了操作方便,账户admin可以通过定义一个自定义的UserAdmin嵌入到默认的用户admin中:

    # admin.py
    from django.contrib import admin
    from .models import Profile
    from django.contrib.auth.models import User


    class UserProfileInline(admin.StackedInline):
        model = Profile


    class UserAdmin(admin.UserAdmin):
        inlines = [UserProfileInline]


    admin.site.unregister(User)
    admin.site.register(User, UserAdmin)

多账户类型

假设在应用中你需要几种类型的用户账户。这里需要有一个字段去跟踪用户使用的是哪一种账户类型。账户数据本身需要存储在独立的模型中,或者存储在一个统一的模型中。

建议使用聚合账户的方法,因为它让改变账户类型而不丢失账户细节,并具有灵活性,减小复杂度。在这个房中中,账户模型包含一个所有账户类型的字段超集。

例如,SuperBook会需要一个SuperHero类型账户,和一个Ordinary(非超集英雄)账户。它可以用一个独立的统一账户模型实现:

    class BaseProfile(models.Model):
        USER_TYPES = (
            (0, 'Ordinary'),
            (1, 'SuperHero'),
        )

        user = models.OnToOneField(settings.AUTH_USER_MODEL, primary_key=True)
        user_type = models.IntegerField(max_length=1, null=True,    choices=USER_TYPES)
        bio = models.CharField(max_length=200, blank=True, null=True)

        def __str__(self):
            return "{}:{:.20}".format(self.user, self.bio or "")

        class Meta:
            abstract = True


    class SuperHeroProfile(models.Model):
        origin = models.CharField(max_length=100, blank=True, null=True)

        class Meta:
            abstract = True


    class OrdinaryProfile(models.Model):
        address = models.CharField(max_length=200, blank=True, null=True)

        class Meta:
            abstract = True


    class Profile(SuperHeroProfile, OrdinaryProfile, BaseProfile):
        pass

我们组织账户细节到多个抽象基类再到独立的关系中。BaseProfile类包含所有的不关心用户类型的公共账户细节。它也有一个user_type字段,它持续追踪用户的激活账户。

SuperHeroProfile类和OrdinaryProfile类分别包含所有到超级英雄和非超级英雄特定账户细节。最后,所有这些基类的profile类创建了一个账户细节的超集。

使用该方法时要主要的一些细节:

所有属于类的字段或者它抽象基类都必须是非空的,或者有一个默认值。  

这个方法或许会为了每个用户而消耗更多的数据库,但是却带来极大的灵活性。  

账户类型的激活和非激活字段都必须在模型外部是可管理的。  

说到,编辑账户的表必须显示合乎目前激活用户类型的字段。  

模式-服务模式

问题:模型会变得庞大而且不可管控。当一个模型不止实现一个功能时,测试和维护也会变得困难。

解决方法:重构出一组相关方法到一个专用的Service对象中。

问题细节

富模型,瘦视图是一个通常要告诉Django新手的格言。理论上,你的视图不应该包含任何其他表现逻辑。

可是,随着时间的推移,代码段不能够放在任意地点,除非你打算将它们放进模型中。很快,模型会变成一个代码的垃圾场。

下面是模型可以Service对象的征兆:

1. 与扩展能服务交互,例如web服务中,检查一个用户具有资格。  
2. 帮助任务不会处理数据库,例如,生成一个短链接,或者针对用户的验证码。  
3. 牵涉到一个短命的对象时不会存在数据库状态记录,烈日,创建一个AJAX调用的JSON响应。  
4. 对于长时间运行的任务设计到了多实例,比如Celery任务。  

Django中的模型遵循着激活记录模式。理论上,它们同时封装应用罗即数据库访问。不过,要记得保持应用逻辑最小化。

在测试时,如果我们发现没有对数据库建模的必要,甚至不会用到数据库,那么我们需要考虑把分解模型类。建议这种场合下使用Service对象。

详解

服务对象是封装一个服务或者和系统狡猾的普通而老旧的Python对象(POPOs)。它们通常保存在一个独立的称为service.py或者utils.py的文件。

例如,像下面这样,检查一个web服务是否作为一个模型方法:

    class Profile(models.Model):
        ...

        def is_superhero(self):
            url = "http://api.herocheck.com/?q={0}".format(
                self.user.username
                )
            return webclient.get(url)

该方法可以使用一个服务对象来重构:

    from .services import SuperHeroWebAPI

    def is_superhero(self):
        return SuperHeroWebAPI.is_superhero(self.user.username)

现在服务对象可以定义在services.py中了:

    API_URL = "http://api.herocheck.com/?q={0}"

    class SuperHeroWebAPI:
        ...

        @staticmehtod
        def is_hero(username):
            url = API_URL.format(username)
            return webclient.get(url)

多数情况下,Service对象的方法是无状态的,即,它们基于函数参数不使用任何的类属性来独自执行动作。因此,最好明确地把它们标记为静态方法(就像我们对is_hero所做的那样)。

可以考虑把业务逻辑和域名逻辑从模型迁移到服务对象中去。这样,你可以在Django应用的外部很好使用它们。
想象一下,由于业务的原因要依据某些用户的名字把这些要成为超级英雄的用户加进黑名单。我们的服务对象稍微改动以下就可以支持这个功能:

    class SuperHeroWebAPI:
        ...

    @staticmethod
    def is_hero(username):
        blacklist = set(["syndrome", "kcka$$", "superfake"])
        ulr = API_URL.format(username)
        return username not in blacklist and webclient.get(url)

理论上,服务对象自包含的。这使它们不用建模——即数据库,也可以易于测试。它们也易于复用。

Django中,耗时服务以Celery这样的异步任务队列方式执行。通常,Service对象以Celery任务的方式执行操作。这样的任务可以周期性地运行或者延迟运行。

检索模式

本节包含处理模型属性的访问,或者对模型执行查询。

模式-属性字段

问题:模型有以方法实现的属性。可是,这些属性不应该保存到数据库。

**解决方案:对这样的方法使用属性装饰器。

问题详情

模型字段存储每个实例的属性,比如名,和姓,生日,等等。它们存储于数据库之中。可是,我们也需要访问某些派生的属性,比如一个完整的名字和年龄。

它们可以轻易地计算数据库字段,因此它们不需要单独地存储。在某些情况下,它们可以成为一个检查所提供的年龄,会员积分,和激活状态是否合格的条件语句。

简洁明了的实现这个方法是定义比如类似get_age这样函数:

    class BaseProfile(models.Model):
        birthdate = models.DateField()
        #...

        def get_age(self):
            today = datetime.date.today()
            return (today.year - self.birthdate.year) - int(
                (today.month, today.day) < (self.birthdate.month, self.birthdate.day)
                )

调用profile.get_age()会通过计算调整过的月和日期所在的那个年份的不同来返回用户的年龄。

不过,这样调用profile.age变得更加可读(和Python范)。

详解

Python类可以使用property装饰器把函数当作一个属性来使用。这样,Django模型也可以较好地利用它。替换前面那个例子中的函数:

@property
def age(self):

现在我们可以用profile.age来访问用户的年龄。注意,函数的名称要尽可能的短。

属性的一个重大缺陷是它对于ORM来说是不可访问的,就像模型的方法那样。你不能够在一个Queryset对象中使用它。例如,这么做是无效的,`Profile.objects.exlude(age__lt=18)。

它也是一个定义一个属性来隐藏类内部细节的好主意。这也正式地称做_得墨忒耳定律_。简单地说,定律声明你应该只访问自己的直属成员或者“仅使用一个点号”。

例如,最好是定义一个profile.birthyear属性,而不是访问profile.birthdate.year。这样,它有助于你隐藏birthdate字段的内在结构。

提示

最佳实践
遵循得墨忒耳定律,并且访问属性时只使用点号

该定律的一个不良反应是它导致在模型中有多个包装器属性被创建。这使模型膨胀并让它们变得难以维护。利用定律来改进你的模型API,减少模型间的耦合,在任何地方都是可行的。

缓存特性

每次我们调用一个属性时,就要重新计算函数。如果计算的代价很大,我们就想到了缓存结果。因此,下次访问属性,我们就拿到了缓存的结果。

    from django.utils.function import cached_property
    #...
    @cached_property
    def full_name(self):
        # 代价高昂的操作,比如,外部服务调用
        return "{0} {1}".format(self.firstname, self.lastname)

缓存的值会作为Python实例的一部分而保存。只要实例一直存在,就会得到同样的返回值。

对于一个保护性机制,你或许想要强制执行高昂代价操作以确保过期的值不会返回。如此情境之下,设置一个cached=False这样的关键字参数能够阻止返回缓存值。

模式-定制模型管理器

问题:某些模型的定义的查询被重复地访问,这彻彻底底的违反了DRY原则。

解决方案:定义自定义的管理器以给常见的查询一个更有意义的名字。

问题细节

每一个Django的模型都有一个默认称做objects的管理器。调用objects.all()会返回数据库中的这个模型的所有条目。通常,我们只对所有条目的子集感兴趣。

我们应用多种过滤器以找出所需的条目组。挑选它们的原则常常是我们的核心业务逻辑。例如,我们发现使用下面的代码可以通过public访问文章:

    public = Posts.objects.filter(privacy="public")

这个标准在未来或许会改变。我们或许也想要检查文章是否标记为编辑。这个改变或许如此:

    public = Posts.objects.filter(privacy=POST_PRIVACY.Public, draft=Flase)

可是,这个改变需要使在任何地方都要用到公共文章。这让人非常沮丧。这仅需要一个定义这样常见地查询而无需“自我重复”。

详解

Querysets是一个极其有用的抽象概念。它们仅在需要时进行惰性查询。因此,通过链式方法(一个流畅的界面)构建更长的Querysets并不影响性能。

事实上,应该更多的过滤会使结果数据集缩减。这样做通常可以减少结果的内存消耗。

模型管理器是一个模型获取自身Queryset对象的便利接口。换句话来讲,它们有助于你使用Django的ORM访问潜在的数据库。事实上,QuerySet对象上管理器以一个非常简单的包装器实现。

    >>> Post.objects.filter(posted_by__username="a")
    [<Post:a: Hello World><Post:a: This is Private!>]

    >>> Post.objects.get_queryset().filter(posted_yb__username="a")
    [<Post:a: Hello World>, <Post:a: This is Private!>]

默认的管理器由Django创建,objects有多种方法返回Queryset,比如allfilter或者exclude。可是,它们生成一个到数据库的低级API。

定制管理器用于创建域名指定,更高级的API。这样不仅更加具有可读性而且通过实现细节减轻影响。因此,你能够在更高级的抽象概念上工作,紧密地模型化到域名。

前面的公共文章例子可以轻松地转换到一个定制的管理器:

    # managers.py
    from django.db.models.query import Queryset


    class PostQuerySet(QuerySet):
        def public_posts(self):
            return self.filter(privacy="public")

    PostManager = PostQuerySet.as_manager

这是一个在Django 1.7中从QuerySet对象创建定制管理器的捷径。不像前面的其他方法,这个PostManager对象像默认的objects管理器一样是链式的。

如下所示,使用我们的定制管理器替换默认的objects管理器也是可行的:

    from .managers import PostManager

    class Post(Postable):
        ...
        objects = PostManager()

这样做,访问public_posts相当简单:

    public = Post.objects.public_posts()

因此返回值是一个QuerySet,它们可以更进一步过滤:

    public_apology = Post.objects.public_posts().filter(
        message_startwith = "Sorry"
        )

QuerySets由多个有趣的属性。在下一章,我们可以看看某些带有合并的QuerySet的常见地模式。

Querysets的组合动作

事实上,对于它们的名字,QuerySets支持多组操作。为了说明,考虑包含用户对象的两个QuerySets

    >>> q1 = User.objects.filter(username__in["a", "b", "c"])
    [<User:a>, <User:b>, <User:c>]
    >>> q2 = User.objects.filter(username__in["c", "d"])
    [<User:c>, <User:d>]

对于一些组合操作可以执行以下动作:

*Union*:合并,移除重复动作。使用`q1`|`q2`获得[`<User: a>, <User: b>, <User: c>, <User: d>`]  

*Intersection*:找出公共项。使用`q1`|`q2`获得[`<User: c>]  

*Difference*:从第一个组中移除第二个组。该操作并不按逻辑来。改用`q1.exlude(pk__in=q2)获得[<User: a>, <User: b>]

同样的操作我们也可以用Q对象来完成:

    from django.db.models import Q

    # Union
    >>> User.objects.filter(Q(username__in["a", "b", "c"]) | Q(username__in=["c", "d"]))
    [`<User: a>, <User: b>, <User: c>, <User: d>`]

    # Intersection
    >>> User.objects.filter(Q(username__in["a", "b", "c"]) & Q(username__in=["c", "d"]))
    [<User: c>]

    # Difference
    >>> User.objects.filter(Q(username__in=["a", "b", "c"]) & ~Q(username__in=["c", "d"]))
    [<User: a>, <User: b>]

注意执行动作所使用&(和)以及~(否定)的不同。Q对象是非常强大的,它可以用来构建非常复杂的查询。

可是,Set虽类似但却不完美。QuerySets不像数学上的集合,那样按照顺序来。因此,在这一方面它们更接近Python的列表数据结构。

链接多个Querysets

目前为止,我们已经合并了属于相同基类的同类型QuerySets。可是,我们或许需要合并来自不同模型的QuestSets,并对它们执行操作。

例如,一个用户的活动时间表包含了它们自身所有的按照反向时间顺序所发布的文章和评论。之前合并的QuerySets方法是不会起作用的。一个较为天真的做法是把它们转换到列表,连接并排列这个列表:

    >>> recent = list(posts)+list(comments)
    >>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3]
    [<Post: user: Post1>, <Comment: user: Comment1>, <Post: user: Post0>]

不幸的是,这个操作已经对惰性的QuerySets对象求值了。两个列表的内存使用算在一起可能很大内存开销。另外,转换一个庞大的QuerySets到列表是很慢很慢的。

一个更好的解决方案是使用迭代器减少内存消耗。如下,使用itertools.chain方法合并多个QuerySets

    >>> from itertools import chain
    >>> recent = chain(posts, comments)
    >>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3]

只要计算QuerySets,连接数据的开销都会非常搞。因此,重要的是,尽可能长的仅有的不对QuerySets求值的操作时间。

提示

尽量延长QuerySets不求值的时间。

迁移

迁移让你改变模型时更有信心。说的Django 1.7,迁移已经是开发流程中基本的易于使用的一部分了。

新的基本流程如下:

1. 第一次定义模型类的话,你需要运行:  

    python manage.py makemigrations <app_label>  


2. 这将在`app/migrations/`文件夹内创建迁移脚本。  
在同样的(开发)环境中运行以下命令:  

    python manage.py migrate <app_label>  


3. 这将对数据库应用模型变更。有时候,遇到的问题有,处理默认值,重命名,等等。  

4. 普及迁移脚本到其他的环境。通常,你的版本控制工具,例如,Git,会小心处理这事。当最新的源释出时,新的迁移脚本也会随之出现。  

5. 在这些环境中运行下面的命令以应用模型的改变:  

    python manage.py migarte <app_label>  


不论何时要将变更应用到模型,请重复以上1-5步骤。

如果你在命令里忽略了app标签,Django会在每一个app中发现未应用的变更并迁移它们。

总结

模型设计要正确地操作很困难。它依然是Django开发的基础。本章,我们学习了使用模型时多个常见模式。每个例子中,我们都见到了建议方案的作用,以及多种折衷方案。

这下一章,我们会用视图和URL配置来验证所遇到的常见设计模式。


© Creative Commons BY-NC-ND 3.0 | 我要订阅 | 我要捐助

第一章-Django与模式

第一章 Django与模式

在这一章,我们讨论以下话题:

 我们为什么选择Django?
 Django是工作原理
 什么是模式?
 常见的模式合集
 Django中的模式

我们为什么选择Django?

每个web应用都不尽相同,就像一件手工制作的家具一样。你几乎会很少发现大批量的生成能够完美地达到你的需求。即使你从一个基本需求开始,比如一个博客或者一个社交网络,你都需要缓慢地开发,

这就是类似Django或者Rails的web框架非常流行的原因。框架加速了开发,而且它带有很多练好的经过实践的内容。

Python可能比其他流行的编程语言具有更多的web框架。

开箱即用的admin接口,它是Django才有的独一无二的特点,早些时候,特别是在数据记录和测试方面它大有裨益。而Django的开发文档作为一个出色的开源项目早已是备受赞誉。

最后,Django在多个高流量的网站中历经实战的考验。它对于常见的攻击比如跨站脚本和跨站请求攻击有着异常敏锐观察。

尽管,在理论上,可能对于所有类型的网站Django不是最佳选择,你可以是使用Django构建任何类型的网站。例如,要构建一个基于web聊天的实时接口,或许你要使用Tornado,但是web引用剩下的部分你可以仍旧使用Django来完成。对于开发你要学会选择正确的工具。

某些内建的特性,比如admin接口,如果你使用过其他的web框架或许让你听上去感觉有点怪怪的。为了Django的设计,就让我们找出它是如何问世的。

Django的历史

When you look at the Pyramids of Egypt, you would think that such a simple and minimal design must have been quite obvious. In truth, they are products of 4,000 years of architectural evolution. Step Pyramids, the initial (and clunky) design, had six rectangular blocks of decreasing size. It took several iterations of architectural and engineering improvements until the modern, glazing, and long-lasting limestone structures were invented.

Looking at Django you might get a similar feeling. So, elegantly built, it must have been 􏰁awlessly conceived. 􏰚n the contrary, it was the result of rewrites and rapid iterations in one of the most high-pressure environments imaginable—a newsroom!

In the fall of 2003, two programmers, Adrian Holovaty and Simon Willison, working at the Lawrence Journal-World newspaper, were working on creating several local news websites in Kansas. These sites, including LJWorld.com, Lawrence.com, and KUsports.com—like most news sites were not just content-driven portals chock-
full of text, photos, and videos, but they also constantly tried to serve the needs of the local Lawrence community with applications, such as a local business directory, events calendar, classifieds, and so on.

一个框架的诞生

This, of course, meant lots of work for Simon, Adrian, and later Jacob Kaplan Moss who had joined their team; with very short deadlines, sometimes with only a few hours' notice. Since it was the early days of web development in Python, they had to write web applications mostly from scratch. So, to save precious time, they gradually refactored out the common modules and tools into something called "The CMS."

Eventually, the content management parts were spun off into a separate project called the Ellington CMS, which went on to become a successful commercial CMS product. The rest of "The CMS" was a neat underlying framework that was general enough to be used to build web applications of any kind.

By July 2005, this web development framework was released as Django (pronounced Jang-Oh) under an open source Berkeley Software Distribution (BSD) license.
It was named after the legendary jazz guitarist Django Reinhardt. And the rest,
as they say, is history.

移除魔法

Due to its humble origins as an internal tool, Django had a lot of Lawrence 􏰈ournal􏰃World􏰃specific oddities. To 􏰀ake Django truly general purpose, an effort dubbed "Removing the Lawrence" was already underway.

However, the 􏰀ost significant refactoring effort that Django developers had to undertake was called "Removing the Magic." This ambitious project involved cleaning up all the warts Django had accumulated over the years, including a lot of magic (an informal term for implicit features) and replacing them with a more natural and explicit Pythonic code. For example, the model classes used to be imported from a magic module called django.models.*, rather than directly importing them from the models.py 􏰀odule they were defined in.

At that time, Django had about a hundred thousand lines of code, and it was a significant rewrite of the 􏰅PI. 􏰚n May 􏰛, 􏰜􏰤􏰤􏰦, these changes, al􏰀ost the si􏰄e of a small book, were integrated into Django's development version trunk and released as Django release 􏰤.􏰧􏰨. This was a significant step toward the Django 􏰛.􏰤

Django坚持做得更好

Every year, conferences called DjangoCons are held across the world for Django developers to meet and interact with each other. They have an adorable tradition
of giving a semi-humorous keynote on "why Django sucks." This could be a member of the Django community, or someone who works on competing web frameworks or just any notable personality.

Over the years, it is amazing how Django developers took these criticisms positively and mitigated them in subsequent releases. Here is a short summary of the improvements corresponding to what once used to be a shortcoming in Django and the release they were resolved in:

• New form-handling library (Django 0.96)
• Decoupling admin from models (Django 1.0)
• Multiple database support (Django 1.2)
• Managing static files better 􏰋Django 􏰛.􏰥􏰌
• Better time zone support (Django 1.4)
• Customizable user model (Django 1.5)
• Better transaction handling (Django 1.6)
• Built-in database migrations (Django 1.7)

Django是如何工作的?

要真正的欣赏Django,你需要撇开表象来看本质。它启发你同时让你不知所措。如果你已经熟悉它,或许就想要跳过这一节。

下图:在Django应用中一个典型的web请求是如何被处理的。
how-web-requests-in-django

前面的图片展示了从一个访客的浏览器到Django应用并返回的一个web请求的简单历程。如下是数字标识的路径:

1. 浏览器发送请求(基本上是字节类型的字符串)到web服务器。

2. web服务器(比如,Nginx)把这个请求转交到一个WSGI(比如,uWSGI),或者直接地文件系统能够取出
一个文件(比如,一个CSS文件)。

3. 不像web服务器那样,WSGI服务器可以直接运行Python应用。请求生成一个被称为environ的Ptyhon字典,
而且,可以选择传递过去几个中间件的层,最终,达到Django应用。

4. URLconf中含有属于应用的urls.py选择一个视图处理基于请求的URL的那个请求,这个请求就已经变成了
HttpRequest——一个Python字典对象。

5. 被选择的那个视图通常要做下面所列出的一件或者更多件事情: 

   通过模型与数据库对话。
   使用模板渲染HTML或者任何格式化过的响应。
   返回一个纯文本响应(不被显示的)。
   抛出一个异常。

6. HttpResponse对象离开Django后,被渲染为一个字符串。

7. 在浏览器见到一个美化的,渲染后的web页面。

虽然某些细节被省略掉,这个解释应该有助于欣赏Django的高级架构。它也展示了关键的组件所扮演的角色,比如模型,视图,和模板。Django的很多组件都基于这几个广为人知设计模式。

什么是模式?

What is co􏰀􏰀on between the words 􏰇Blueprint,􏰇 􏰇􏰆caffolding,􏰇 and 􏰇Maintenance􏰇? These software development terms have been borrowed from the world of building construction and architecture. However, one of the 􏰀ost in􏰁uential ter􏰀s co􏰀es from a treatise on architecture and urban planning written in 1977 by the leading Austrian architect Christopher Alexander and his team consisting of Murray Silverstein, Sara Ishikawa, and several others.

The term "Pattern" came in vogue after their seminal work, A Pattern Language: Towns, Buildings, Construction 􏰋volu􏰀e 􏰜 in a five􏰃book series􏰌 based on the astonishing insight that users know about their buildings more than any architect ever could. A pattern refers to an everyday problem and its proposed but time-tested solution.

In the book, Christopher Alexander states that "Each pattern describes a problem, which occurs over and over again in our environment, and then describes the core of the solution to that problem in such a way that you can use this solution
a million times over, without ever doing it the same way twice."

For example, the Wings Of Light pattern describes how people prefer buildings with more natural lighting and suggests arranging the building so that it is composed of wings. These wings should be long and narrow, never more than 25 feet wide. Next time you enjoy a stroll through the long well-lit corridors of an old university, be grateful to this pattern.

Their book contained 253 such practical patterns, from the design of a room to the design of entire cities. Most importantly, each of these patterns gave a name to an abstract problem and together formed a pattern language.

Re􏰀e􏰀ber when you first ca􏰀e across the word déjà vu? You probably thought "Wow, I never knew that there was a word for that experience." Similarly, architects were not only able to identify patterns in their environ􏰀ent but could also, finally, name them in a way that their peers could understand.

In the world of software, the term design pattern refers to a general repeatable solution to a commonly occurring problem in software design. It is a formalization of best practices that a developer can use. Like in the world of architecture, the pattern language has proven to be extremely helpful to communicate a certain way of solving a design problem to other programmers.

There are several collections of design patterns but some have been considerably 􏰀ore in􏰁uential than the others.

四人组模式

One of the earliest efforts to study and document design patterns was a book
titled Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, who later became known as the Gang of Four (GoF). This book is so in􏰁uential that 􏰀any consider the 􏰜􏰥 design patterns in the book as fundamental to software engineering itself.

In reality, the patterns were written primarily for object-oriented programming languages, and it had code examples in C++ and Smalltalk. As we will see shortly, many of these patterns might not be even required in other programming languages with better higher-order abstractions such as Python.

The 􏰜􏰥 patterns have been broadly classified by their type as follows􏰂:

• Creational Patterns: These include Abstract Factory, Builder Pattern, Factory Method, Prototype Pattern, and Singleton Pattern
• Structural Patterns: These include Adapter Pattern, Bridge Pattern, Composite Pattern, Decorator Pattern, Facade Pattern, Flyweight Pattern, and Proxy Pattern
• Behavioral Patterns: These include Chain of Responsibility, Command Pattern, Interpreter Pattern, Iterator Pattern, Mediator Pattern, Memento Pattern, Observer Pattern, State Pattern, Strategy Pattern, Template Pattern, and Visitor Pattern

While a detailed explanation of each pattern would be beyond the scope of this book, it would be interesting to identify some of these patterns in Django itself:

Django中的模式与此对比:

四人组模式 Django组件 解释
命令模式 HttpRequest 把一个request封装进一个对象
观察者模式 Signals 一个对象改变状态时,它的所有侦听器都被通知并自动更新
模板模式 基于类的视图 一个算法的步骤可以不用改变算法结构来重新定义子类

而这些模式是对于学习Django内部的人来说构造最有趣的,Django下面的模式可以再次分类——这也是个常见的问题。

Django是MVC吗?

Model-View-Controller (MVC) is an architectural pattern invented by Xerox PARC in the 70s. Being the framework used to build user interfaces in Smalltalk, it gets an early mention in the GoF book.

Today, MVC is a very popular pattern in web application frameworks. Beginners often ask the question􏰩is Django an M􏰊􏰖 fra􏰀ework?

The answer is both yes and no. The MVC pattern advocates the decoupling of
the presentation layer from the application logic. For instance, while designing
an online game website API, you might present a game's high scores table as an HTML, 􏰪ML, or co􏰀􏰀a􏰃separated 􏰋􏰖􏰆􏰊􏰌 file. However, its underlying 􏰀odel class would be designed independent of how the data would be finally presented.
MVC is very rigid about what models, views, and controllers do. However, Django takes a much more practical view to web applications. Due to the nature of the HTTP protocol, each request for a web page is independent of any other request. Django's framework is designed like a pipeline to process each request and prepare a response.

Django calls this the Model-Template-View (MTV) architecture. There is separation of concerns between the database interfacing classes (Model), request-processing classes 􏰋􏰊iew􏰌, and a te􏰀plating language for the final presentation 􏰋Te􏰀plate􏰌.

If you compare this with the classic MVC—"Model" is comparable to Django's Models, "View" is usually Django's Templates, and "Controller" is the framework itself that processes an incoming HTTP request and routes it to the correct view function.

If this has not confused you enough, Django prefers to name the callback function to handle each URL a "view" function. This is, unfortunately, not related to the MVC pattern's idea of a View.

MVC对于模型,视图,和控制该做什么设定的非常严格。然而,到web应用Django采取的是更实用的视图。要处理原生的HTTP协议,每个到web页面的请求独立于其他的请求。Django的框架设计的像一个管道处理每个请求,准备好响应。

Django称之为模型-模板-视图(MTV)架构。数据库接口类(模型)和 请求处理类(视图),以及最终表现的模板语言之间有着独立关系。

如果你将它于经典的MVC比较,“模型”可以通Django的模型比较,“视图”

要是这些都还不足以让你感到迷惑,Django倾向于选择命名回调函数处理每个URL的“视图”函数。即,一个没有MVC视图概念的视图。

福勒模式

In 2002, Martin Fowler wrote Patterns of Enterprise Application Architecture, which described 40 or so patterns he often encountered while building enterprise applications.

Unlike the GoF book, which described design patterns, Fowler's book was about architectural patterns. Hence, they describe patterns at a much higher level of abstraction and are largely programming language agnostic.
Fowler's patterns are organized as follows:

• Domain Logic Patterns: These include Domain Model, Transaction Script, Service Layer , and Table Module
• Data Source Architectural Patterns: These include Row Data Gateway, Table Data Gateway, Data Mapper, and Active Record
• Object-Relational Behavioral Patterns: These include Identity Map, Unit of Work, and Lazy Load
• Object-Relational Structural Patterns: These include Foreign Key Mapping, Mapping, Dependent Mapping, Association Table Mapping, Identity
Field, Serialized LOB, Embedded Value, Inheritance Mappers, Single Table Inheritance, Concrete Table Inheritance, and Class Table Inheritance
• Object-Relational Metadata Mapping Patterns: These include Query Object, Metadata Mapping, and Repository
• Web Presentation Patterns: These include Page Controller, Front Controller, Model View Controller, Transform View, Template View, Application Controller, and Two-Step View
• Distribution Patterns: These include Data Transfer Object and Remote Facade
• Offline Concurrency Patterns: These include Coarse Grained Lock, Implicit Lock, Optimistic 􏰚f􏰁ine Lock, and Pessi􏰀istic 􏰚f􏰁ine Lock
• Session State Patterns: These include Database Session State, Client Session State, and Server Session State
• Base Patterns: These include Mapper, Gateway, Layer Supertype, Registry, Value Object, Separated Interface, Money, Plugin, Special Case, Service Stub, and Record Set

Almost all of these patterns would be useful to know while architecting a Django application. In fact, Fowler's website at http://martinfowler.com/eaaCatalog/ has an excellent catalog of these patterns. I highly recommend that you check them out.
Django also implements a number of these patterns. The following table lists a few of them:

Django中的模式与此对比:

四人组模式 Django组件 解释
活动记录 Django模型 封装数据库访问,对数据添加域名逻辑
类表继承 模型继承 继承中的每个实体都映射到一个独立的表
标识自动 Id字段 在一个对象中保存一个数据库ID字段以维护标识
模板视图 Django模板 使用HTML的内嵌生成器渲染到HTML

还有更多的模式?

Yes, of course. Patterns are discovered all the ti􏰀e. Like living beings, so􏰀e mutate and form new patterns: take, for instance, MVC variants such as Model–view–presenter (MVP), Hierarchical model–view–controller (HMVC), or Model View ViewModel (MVVM).

Patterns also evolve with ti􏰀e as better solutions to known proble􏰀s are identified. For example, Singleton pattern was once considered to be a design pattern but now is considered to be an Anti-pattern due to the shared state it introduces, similar to using global variables. An Anti-pattern can be defined as co􏰀􏰀only reinvented but a bad solution to a problem.

Some of the other well-known books which catalog patterns are Pattern-Oriented Software Architecture (known as POSA) by Buschmann, Meunier, Rohnert, Sommerlad, and Sta; Enterprise Integration Patterns by Hohpe and Woolf; and
The Design of Sites: Patterns, Principles, and Processes for Crafting a Customer-Centered Web Experience by Duyne, Landay, and Hong.

本书中模式

This book will cover Django􏰃specific design and architecture patterns, which would be useful to a Django developer. The upcoming sections will describe how each pattern will be presented.

Pattern name
The heading is the pattern name. If it is a well-known pattern, the commonly used name is used; otherwise, a terse, self-descriptive name has been chosen. Names are important, as they help in building the pattern vocabulary. All patterns will have the following parts:
Problem:􏰂 This brie􏰁y 􏰀entions the proble􏰀.

Solution: This summarizes the proposed solution(s).

Problem Details: This elaborates the context of the problem and possibly gives an example.
Solution Details: This explains the solution(s) in general terms and provides a sample Django implementation.

本书覆盖针对对于Django开发者会很有用的Django的设计和架构模式。接下来的章节会描述每个模式是如何实现的。

模式名称
标题是模式名称。如果它是知名的模式,常用的名字被使用;否则,简洁的,自描述的名称被选择。名称非常重要,它们有助于构建模式词汇。所有的模式都有以下部分:

鉴审模式

Despite their near universal usage, Patterns have their share of criticism too. The most common arguments against them are as follows:

• Patterns compensate for the missing language features: Peter Norvig found that 16 of the 23 patterns in Design Patterns were 'invisible or simpler' in Lisp. Considering Python's introspective facilities and first􏰃class functions, this 􏰀ight as well be the case for Python too.
• Patterns repeat best practices: Many patterns are essentially formalizations of best practices such as separation of concerns and could seem redundant.
• Patterns can lead to over-engineering: Implementing the pattern might be less efficient and excessive co􏰀pared to a si􏰀pler solution.

尽管它们近乎于通用,模式共享也有它们的审鉴共享。它们的最常见参数如下:

如何使用模式

While some of the previous criticisms are quite valid, they are based on how patterns are misused. Here is some advice that can help you understand how best to use design patterns:

• Don't implement a pattern if your language supports a direct solution
• Don't try to retro􏰃fit everything in ter􏰀s of patterns
• Use a pattern only if it is the most elegant solution in your context
• Don't be afraid to create new patterns

最佳实践

In addition to design patterns, there might be a recommended approach to solving a problem. In Django, as with Python, there might be several ways to solve a problem but one idiomatic approach among those.

Python之禅和Django的设计哲学

Generally, the Python community uses the term 'Pythonic' to describe a piece of idiomatic code. It typically refers to the principles laid out in 'The Zen of Python'. Written like a poem, it is extremely useful to describe such a vague concept.

Try entering import this in a Python prompt to view 'The Zen of Python'.

Furthermore, Django developers have crisply documented their design philosophies while designing the framework at https://docs.djangoproject.com/en/dev/ misc/design-philosophies/.

While the document describes the thought process behind how Django was designed, it is also useful for developers using Django to build applications. Certain principles such as Don't Repeat Yourself (DRY), loose coupling, and tight cohesion can help you write more maintainable and idiomatic Django applications.

Django or Python best practices suggested by this book would be formatted in the following manner:

Best Practice:

Use BASE_DIR in settings.py and avoid hard-coding directory names.

最佳实践:

在settings.py中使用BASE_DIR,避免硬编码目录名称。

总结

In this chapter, we looked at why people choose Django over other web frameworks, its interesting history, and how it works. We also examined design patterns, popular pattern collections, and best practices.

In the next chapter, we will take a look at the first few steps in the beginning of a Django project such as gathering requirements, creating mockups, and setting up the project.


© Creative Commons BY-NC-ND 3.0 | 我要订阅 | 我要捐助

第八章-处理旧版本代码

第八章-处理旧版本代码


In this chapter, we will discuss the following topics:

• Reading a Django code base
• Discovering relevant documentation
• Incremental changes versus full rewrites
• Writing tests before changing code
• Legacy database integration

It sounds exciting when you are asked to join a project. Powerful new tools and cutting-edge technologies might await you. However, quite often, you are asked to work with an existing, possibly ancient, codebase.

To be fair, Django has not been around for that long. However, projects written for older versions of Django are sufficiently different to cause concern. 􏰆o􏰀eti􏰀es, having the entire source code and documentation might not be enough.

􏰇Yeah,􏰇 said Brad, 􏰇Where is Hart?􏰇 Mada􏰀 􏰚 hesitated and replied, "Well, he resigned. Being the head of IT security, he took moral responsibility of the perimeter breach." Steve, evidently shocked,
was shaking his head. "I am sorry," she continued, "But I have been assigned to head SuperBook and ensure that we have no roadblocks to meet the new deadline."

There was a collective groan. Undeterred, Madam O took one of the sheets and began, "It says here that the Remote Archive module is the most high-priority item in the incomplete status. I believe Evan is working on this."
If you are asked to recreate the environment, then you might need to fumble with the 􏰚􏰆 configuration, database settings, and running services locally or on the network. There are so many pieces to this puzzle that you might wonder how and where to start.

Understanding the Django version used in the code is a key piece of information. As Django evolved, everything from the default project structure to the recommended best practices have changed. Therefore, identifying which version of Django was used is a vital piece in understanding it.

Change of Guards

Sitting patiently on the ridiculously short beanbags in the training room, the SuperBook team waited for Hart. He had convened an emergency go-live meeting. Nobody understood the "emergency" part since go live was at least 3 months away.

Madam O rushed in holding a large designer coffee mug in one hand and a bunch of printouts of what looked like project timelines in the other. Without looking up she said, "We are late so I will get straight to the point. In the light of last week's attacks, the board has decided to summarily expedite the SuperBook project and has set the deadline to end of next 􏰀onth. 􏰅ny questions?􏰇

Yeah,􏰇 said Brad, 􏰇Where is Hart?􏰇 Mada􏰀 􏰚 hesitated and replied, "Well, he resigned. Being the head of IT security, he took moral responsibility of the perimeter breach." Steve, evidently shocked,
was shaking his head. "I am sorry," she continued, "But I have been assigned to head SuperBook and ensure that we have no roadblocks to meet the new deadline."

There was a collective groan. Undeterred, Madam O took one of the sheets and began, "It says here that the Remote Archive module is the most high-priority item in the incomplete status. I believe Evan is working on this."

"That's correct," said Evan from the far end of the room. "Nearly there," he smiled at others, as they shifted focus to him. Madam O peered above the rim of her glasses and smiled almost too politely. "Considering that we already have an extremely well-tested and working Archiver in our Sentinel code base, I would recommend that you leverage that instead of creating another redundant system."

"But," Steve interrupted, "it is hardly redundant. We can improve over a legacy archiver, can't we?􏰇 􏰇If it isn't broken, then don't fix it􏰇, replied Madam O tersely. He said, "He is working on it," said Brad almost shouting, 􏰇What about all that work he has already finished?􏰇

􏰇􏰍van, how 􏰀uch of the work have you co􏰀pleted so far?􏰇 asked 􏰚, rather impatiently. "About 12 percent," he replied looking defensive. 􏰍veryone looked at hi􏰀 incredulously. 􏰇What? That was the hardest 􏰛􏰜 percent" he added.

O continued the rest of the meeting in the same pattern. Everybody's work was reprioriti􏰄ed and shoe􏰃horned to fit the new deadline. 􏰅s she picked up her papers, readying to leave she paused and removed her glasses.

"I know what all of you are thinking... literally. But you need to know that we had no choice about the deadline. All I can tell you now is that the world is counting on you to meet that date, somehow or other." Putting her glasses back on, she left the room.

"I a􏰀m definitely going to bring 􏰀my tinfoil hat,􏰇" said E􏰍van loudly to himself.

Finding the Django version

Ideally, every project will have a requirements.txt or setup.py file at the root directory, and it will have the exact version of Django used for that project. Let's look for a line similar to this:

Django==1.5.9

Note that the version number is exactly mentioned (rather than Django>=1.5.9), which is called pinning. Pinning every package is considered a good practice since it reduces surprises and makes your build more deterministic.

Unfortunately, there are real-world codebases where the requirements.txt file was not updated or even completely missing. In such cases, you will need to probe for various tell􏰃tale signs to find out the exact version.

Activating the virtual environment

In most cases, a Django project would be deployed within a virtual environment. Once you locate the virtual environment for the project, you can activate it by jumping to that directory and running the activated script for your OS. For Linux, the command is as follows:

$ source venv_path/bin/activate

Once the virtual environment is active, start a Python shell and query the Django version as follows:

$ python
>>> import django
>>> print(django.get_version())
1.5.9

The Django version used in this case is Version 1.5.9.

Alternatively, you can run the manage.py script in the project to get a similar output:

$ python manage.py --version
1.5.9

However, this option would not be available if the legacy project source snapshot was sent to you in an undeployed form. If the virtual environment (and packages) was also included, then you can easily locate the version number (in the form of a tuple) in the __init__.py file of the Django directory. For exa􏰀mple􏰂:

$ cd envs/foo_env/lib/python2.7/site-packages/django
$ cat __init__.py
VERSION = (1, 5, 9, 'final', 0)
...

If all these methods fail, then you will need to go through the release notes of
the past Django versions to deter􏰀ine the identifiable changes 􏰋for exa􏰀ple, the AUTH_PROFILE_MODULE setting was deprecated since Version 1.5) and match them to your legacy code. Once you pinpoint the correct Django version, then you can move on to analyzing the code.

文件都放在哪里了?这可不是PHP哦

最大的一个困难点是用到的场合,特别是如果你来自PHP或者APS.NET的世界,即,源文件并不位于web服务器的文档根目录,而目录却通常命名为wwwroot或者public_html。此外,代码的目录结构和网站的URL结构之间并没有直接的关系。

In fact, you will find that your Django website's source code is stored in an obscure path such as /opt/webapps/my-django-app. Why is this? 􏰅􏰀ong 􏰀any good reasons, it is often 􏰀ore secure to 􏰀ove your confidential data outside your public webroot. This way, a web crawler would not be able to accidentally stumble into your source code directory.

As you would read in the Chapter 11, Production-ready the location of the source code can be found by exa􏰀ining your web server's configuration file. Here, you will find either the environment variable DJANGO_SETTINGS_MODULE being set to the module's path, or it will pass on the request to a W􏰆􏰢I server that will be configured to point to your project.wsgi file.

Starting with urls.py

Even if you have access to the entire source code of a Django site, figuring out how it works across various apps can be daunting. It is often best to start from the root urls.py URLconf file since it is literally a 􏰀ap that ties every request to the respective views.

With normal Python programs, I often start reading from the start of its execution—say, from the top-level main module or wherever the main check idiom starts. In the case of Django applications, I usually start with urls.py since it is easier to follow the 􏰁ow of execution based on various URL patterns a site has.

In Linux, you can use the following find command to locate the settings.py file and the corresponding line specifying the root urls.py:

$ find . -iname settings.py -exec grep -H 'ROOT_URLCONF' {} \;
./projectname/settings.py:ROOT_URLCONF = 'projectname.urls'
$ ls projectname/urls.py
projectname/urls.py

Jumping around the code

Reading code sometimes feels like browsing the web without the hyperlinks. When you encounter a function or variable defined elsewhere, then you will need to ju􏰀p to the file that contains that definition. 􏰆o􏰀e ID􏰍s can do this auto􏰀atically for you as long as you tell it which files to track as part of the project.

If you use 􏰍􏰀Emacs or 􏰊Vim􏰀 instead, then you can create a T􏰅􏰢􏰆AGS file to quickly navigate between files. 􏰢Go to the project root and run a tool called Exuberant Ctags as follows:

find . -iname "*.py" -print | etags -

This creates a file called T􏰅􏰢􏰆AGS that contains the location infor􏰀ation, where every syntactic unit such as classes and functions are defined. In 􏰍􏰀Emacs, you can find the definition of the tag, where your cursor 􏰋or point as it called in 􏰍􏰀acs􏰌 is at using the M-. command.

While using a tag file is extre􏰀ely fast for large code bases, it is quite basic and is not aware of a virtual environ􏰀ent 􏰋where 􏰀ost definitions 􏰀ight be located􏰌. 􏰅n excellent alternative is to use the elpy package in 􏰍􏰀acs. It can be configured to detect a virtual environ􏰀ent. 􏰈u􏰀ping to a definition of a syntactic ele􏰀ent is using the same M-. co􏰀􏰀and. However, the search is not restricted to the tag file. 􏰆o, you can even ju􏰀p to a class definition within the Django source code sea􏰀lessly.

Understanding the code base

It is quite rare to find legacy code with good documentation. Even if you do, the documentation might be out of sync with the code in subtle ways that can lead to further issues. Often, the best guide to understand the application's functionality is the executable test cases and the code itself.

The official Django docu􏰀entation has been organi􏰄ed by versions at https://docs. djangoproject.com. On any page, you can quickly switch to the corresponding page in the previous versions of Django with a selector on the bottom right-hand section of the page:

图片:略

In the same way, documentation for any Django package hosted on readthedocs. org can also be traced back to its previous versions. For example, you can select the documentation of django-braces all the way back to v1.0.0 by clicking on the selector on the bottom left-hand section of the page:

图片:略

Creating the big picture

Most people find it easier to understand an application if you show them a high-level diagram. While this is ideally created by someone who understands the workings of the application, there are tools that can create very helpful high-level depiction of a Django application.

A graphical overview of all models in your apps can be generated by
the graph_models management command, which is provided by the django-command-extensions package. As shown in the following diagram, the model classes and their relationships can be understood at a glance:

图片:略

Model classes used in the SuperBook project connected by arrows indicating their relationships

This visualization is actually created using PyGraphviz. This can get really large
for projects of even medium complexity. Hence, it might be easier if the applications are logically grouped and visualized separately.

PyGraphviz Installation and Usage

If you find the installation of Py􏰢raphvi􏰄 challenging, then don't worry, you are not alone. Recently, I faced numerous issues while installing on Ubuntu, starting from Python 3 incompatibility to incomplete documentation. To save your time, I have listed the steps that worked for me to reach a working setup.

On Ubuntu, you will need the following packages installed to install PyGraphviz:

$ sudo apt-get install python3.4-dev graphviz libgraphviz-dev pkg-config

Now activate your virtual environment and run pip to install the development version of PyGraphviz directly from GitHub, which supports Python 3:

$ pip install git+http://github.com/pygraphviz/pygraphviz.git#egg=pygraphviz

Next, install django-extensions and add it to your INSTALLED_ APPS. Now, you are all set.

Here is a sa􏰀ple usage to create a 􏰢raph􏰊i􏰄 dot file for just two apps and to convert it to a PNG image for viewing:

$ python manage.py graph_models app1 app2 > models.dot
$ dot -Tpng models.dot -o models.png

Incremental change or a full rewrite?

Often, you would be handed over legacy code by the application owners in the earnest hope that most of it can be used right away or after a couple of minor tweaks. However, reading and understanding a huge and often outdated code base is not an easy job. Unsurprisingly, most programmers prefer to work on greenfield develop􏰀ent.

In the best case, the legacy code ought to be easily testable, well documented,
and 􏰁exible to work in 􏰀odern environ􏰀ents so that you can start 􏰀aking incremental changes in no time. In the worst case, you might recommend discarding the existing code and go for a full rewrite. Or, as it is commonly decided, the short-term approach would be to keep making incremental changes, and a parallel long-term effort might be underway for a complete reimplementation.

A general rule of thumb to follow while taking such decisions is—if the cost of rewriting the application and maintaining the application is lower than the cost of maintaining the old application over time, then it is recommended to go for a rewrite. Care must be taken to account for all the factors, such as time taken to get new programmers up to speed, the cost of maintaining outdated hardware, and so on.

Sometimes, the complexity of the application domain becomes a huge barrier against a rewrite, since a lot of knowledge learnt in the process of building the older code gets lost. Often, this dependency on the legacy code is a sign of poor design in the application like failing to externalize the business rules from the application logic.

The worst form of a rewrite you can probably undertake is a conversion, or a mechanical translation from one language to another without taking any advantage of the existing best practices. In other words, you lost the opportunity to modernize the code base by removing years of cruft.

Code should be seen as a liability not an asset. As counter-intuitive as it might sound, if you can achieve your business goals with a lesser amount of code, you have dramatically increased your productivity. Having less code to test, debug, and maintain can not only reduce ongoing costs but also make your organization 􏰀ore agile and 􏰁exible to change.

Code is a liability not an asset. Less code is more maintainable.

Irrespective of whether you are adding features or trimming your code, you must not touch your working legacy code without tests in place.

Write tests before making any changes

In the book Working Effectively with Legacy Code, Michael Feathers defines legacy code as, simply, code without tests. He elaborates that with tests one can easily 􏰀odify the behavior of the code quickly and verifiably. In the absence of tests, it is impossible to gauge if the change made the code better or worse.

􏰚ften, we do not know enough about legacy code to confidently write a test. Michael recommends writing tests that preserve and document the existing behavior, which are called characterization tests.

Unlike the usual approach of writing tests, while writing a characterization test, you will first write a failing test with a du􏰀􏰀y output, say X, because you don't know what to expect. When the test harness fails with an error, such as "Expected output X but got Y", then you will change your test to expect Y. So, now the test will pass, and it becomes a record of the code's existing behavior.

Note that we might record buggy behavior as well. After all, this is unfamiliar code. Nevertheless, writing such tests are necessary before we start changing the code. Later, when we know the specifications and code better, we can fix these bugs and update our tests (not necessarily in that order).

Step-by-step process to writing tests

Writing tests before changing the code is similar to erecting scaffoldings before the restoration of an old building. It provides a structural framework that helps you confidently undertake repairs.

You 􏰀ight want to approach this process in a stepwise 􏰀anner as follows􏰂:

1. Identify the area you need to make changes to. Write characterization tests focusing on this area until you have satisfactorily captured its behavior.
2. Look at the changes you need to 􏰀ake and write specific test cases for those. Prefer smaller unit tests to larger and slower integration tests.
3. Introduce incremental changes and test in lockstep. If tests break, then try to analyze whether it was expected. Don't be afraid to break even the characterization tests if that behavior is something that was intended
to change.

If you have a good set of tests around your code, then you can quickly find the effect of changing your code.

On the other hand, if you decide to rewrite by discarding your code but not your data, then Django can help you considerably.

Legacy databases

There is an entire section on legacy databases in Django documentation and rightly so, as you will run into them many times. Data is more important than code, and databases are the repositories of data in most enterprises.

You can 􏰀oderni􏰄e a legacy application written in other languages or frameworks by importing their database structure into Django. As an immediate advantage, you can use the Django admin interface to view and change your legacy data.

Django makes this easy with the inspectdb management command, which looks as follows:

$ python manage.py inspectdb > models.py

当你的设置文件使用旧版本的数据库配置过了,这个命令可以自动地生成应用到模型文件的Python代码。

如果你正在把该方法集成到就旧版本数据库,这里给你一些最佳实践建议:

• Know the limitations of Django ORM beforehand. Currently, multicolumn (composite) primary keys and NoSQL databases are not supported.
• Don't forget to manually clean up the generated models, for example, remove the redundant 'ID' fields since Django creates the􏰀 auto􏰀atically.
• Foreign 􏰣ey relationships 􏰀ay have to be 􏰀anually defined. In so􏰀e databases, the auto􏰃generated 􏰀odels will have the􏰀 as integer fields 􏰋suffixed with _id).
• Organize your models into separate apps. Later, it will be easier to add the views, forms, and tests in the appropriate folders.
• Remember that running the migrations will create Django's administrative tables (django_* and auth_*) in the legacy database.

In an ideal world, your auto-generated models would immediately start working, but in practice, it takes a lot of trial and error. Sometimes, the data type that Django inferred might not match your expectations. In other cases, you might want to add additional meta information such as unique_together to your model.

Eventually, you should be able to see all the data that was locked inside that aging PHP application in your familiar Django admin interface. I am sure this will bring a smile to your face.

Summary

In this chapter, we looked at various techniques to understand legacy code. Reading code is often an underrated skill. But rather than reinventing the wheel, we need
to judiciously reuse good working code whenever possible. In this chapter and the rest of the book, we emphasize the importance of writing test cases as an integral part of coding.

In the next chapter, we will talk about writing test cases and the often frustrating task of debugging that follows.

第二章-应用设计

第二章 应用模式

本章,我们将学习以下内容:

  • 获取请求
  • 创建一个概念文档
  • 如何将一个项目分为多个app
  • 是重新写一个新的app还是使用已有的
  • 开始一个项目之前的最佳实践
  • 为什是Python3?
  • 启动SuperBook项目

很多的开发新手从按正确方式写代码开始,着手一个新的项目。更多的是经常被带到错误的设想中,未被使用的特性,并且浪费掉很多时间。花些时间在你的客户端上,来理解一个项目中的核心请求事件,即使很短的时间也能产生惊人的效果。管理请求是一个值得学习的关键知识点。

如何获取请求

创新不关乎肯定一切,而是关乎对所有重要的特性说不 ---Steve Jobs

我通过与客户耗去数天来仔细地的倾听他们的需求,以及合理的期望值,拯救了好几个注定失败的项目。除了纸(或者他们的数字设备)和一支笔之外什么也没用,处理过程惊人的简单但是却很有效。这里有一些获取请求的关键地方:

1. 直接和应用的所有者沟通即使他们没有技术背景。  
2. 确保你完整的倾听他们的需求并提醒他们。  
3. 不要使用技术术语比如“模型”。保证用语简单,使用终端用户能理解的术语比如“用户账户”。  
4.合理的期望值。如果某件事情在技术行不可行,或者很难实现,要保证你以正确的方法告知他们。  
5.描述的尽可能具象。在大自然中人类是依靠视觉。网站更是如此。使用粗线条和图形表。不需要多么的完美。  
6.分解处理流程,比如用户注册。任何多步骤的功能都需要以用箭头连接的方框画出。
7.最后,解决用户叙述表格中的特性,
8.扮演一个活动的角色
9.在融合新特性上要非常,非常地保守。
10.开会,和大家分享你的笔记以避免误解。  

第一个会议会比较长(可能是一整个工作日,或者好几个小时)。之后,当这些会议的沟通变的顺畅时,你可以将它们削减到30分钟或者一个小时。

所有的这一切的结果会是写了满满的一整页,以及好几张的乱糟糟的草图。

在本书,我们已经着手建构我们自己的一个为超级英雄而构建的称做SuperBook的社会网络。A simple sketch based off our discussions with a bunch of randomly selected superheroes is shown as follows:


2015-05-28 14 27 35

按照响应式设计的SuperBook网站草图。桌面(左边)和手机(右边)的效果如上.

你是一个会讲故事的人吗?

那么这一章写的是什么呢?这是一个解释使用这个网站是做何感觉的简易文档。

注释

SuperBook的概念
下面的访问记录是在未来我们的网站SuperBook运营之后所做的整理。

请先介绍下你自己
My name is Aksel. I am a gray squirrel living in downtown New York. However, everyone calls 􏰀e 􏰆corn. My dad, T. Berry, a fa􏰀ous hip-hop star, used to call me that. I guess I was never good enough
at singing to take up the family business.

Actually, in my early days, I was a bit of a kleptomaniac. I am allergic to nuts, you know. Other bros have it easy. They can just live off any park. I had to improvise—cafes, movie halls, amusement parks, and so on. I read labels very carefully too.

*Ok, Acorn. Why do you think you were chosen for the user testing? *

Probably, because I was featured in a 􏰍Y 􏰊tar special on lesser􏰉known superheroes. I guess people find it a􏰀using that a squirrel can use
a MacBook (Interviewer: this interview was conducted over chat). Plus, I have the attention span of a squirrel.

*Based on what you saw, what is your opinion about SuperBook? *

I think it is a fantastic idea. I mean, people see superheroes all the time. However, nobody cares about them. Most are lonely and antisocial. SuperBook could change that.

What do you think is different about Superbook?

It is built from the ground up for people like us. I mean, there is no "Work and Education" nonsense when you want to use your secret identity. Though I don't have one, I can understand why one would.

Could you tell us briefly some of the features you noticed?

Sure, I think this is a pretty decent social network, where you can:

• Sign up with any user name (no more, "enter your real name", silliness)
• Fans can follow people without having to add them as "friends"
• Make posts, comment on them, and re-share them
• Send a private post to another user

E􏰋verything is easy. It doesn't take a superhu􏰀an effort to figure it out. Thanks for your time, Acorn.

HTML建模

在构建web应用的前几天,像Photoshop和Flash这样的工具做到像素级别的模型效果使用是非常广泛的。 They are hardly recommended or used anymore.

Giving a native and consistent experience across smartphones, tablets, laptops, and other platforms is now considered more important than getting that pixel-perfect look. In fact, most web designers directly create layouts on HTML.

Creating an HTML mockup is a lot faster and easier than before. If your web designer is unavailable, developers can use a CSS framework such as Bootstrap or ZURB Foundation framework to create pretty decent mockups.

The goal of creating a mockup is to create a realistic preview of the website. It should not 􏰀erely focus on details and polish to look closer to the final product compared to a sketch, but add interactivity as well. Make your static HTML come to life with working links and some simple JavaScript-driven interactivity.

A good mockup can give 80 percent of customer experience with less than 20 percent of the overall development effort.

设计应用

当你对于自己所要构建的东西有相当好的构思时,你可以开始思考如何使用Django来实现它。再者,这可以诱导人开始编程。然而,当你花了几分钟时间来思考设计时,你可以发现非常多的不同方法来解决一个设计问题。

一如测试驱动的开发方法论中提倡的那样,你也可以首先从测试开始。我们会在测试章节见到更多的TDD方法。我们会在测试那章见到更多的TDD方法。

Whichever approach you take, it is best to stop and think—"Which are the different ways in which I can i􏰀ple􏰀ent this? What are the trade􏰉offs? Which factors are 􏰀ore i􏰀portant in our context? Finally, which approach is the best?􏰎

Experienced Django developers look at the overall project in different ways. 􏰊ticking to the DRY principle 􏰐or so􏰀eti􏰀es because they get la􏰓y􏰑, they think 􏰌􏰎Have I seen this functionality before? For instance, can this social login feature be implemented using a third-party package such as django-all-auth?􏰎
If they have to write the app themselves, they start thinking of various design patterns in the hope of an elegant design. However, they first need to break down a project at the top level into apps。

将一个项目分离为多个app

Django的应用称做_project_。一个项目由多个应用或者_apps_组成。应用是具有一组特性的Python包。

理论上说,每个app都必须是可以重复使用的。你可以按照自己的需要创建尽可能多的app。绝对不要害怕添加跟多的app,或者重构一个已经存在的应用到多个app。一个典型的Djanog项目包含15到20个app。

An important decision to make at this stage is whether to use a third-party Django app or build one from scratch. Third-party apps are ready-to-use apps, which are not built by you. Most packages are quick to install and set up. You can start using them in a few minutes.

On the other hand, writing your own app often means designing and implementing the models, views, test cases, and so on yourself. Django will make no distinction between apps of either kind.

用自己写的,还是使用别人的

One of Django's biggest strengths is the huge ecosystem of third-party apps. At the time of writing, djangopackages.com lists 􏰀ore than 􏰁,􏰅􏰂􏰂 packages. You 􏰀ight find that your co􏰀pany or personal library has even 􏰀ore. 􏰔nce your project is broken into apps and you know which kind of apps you need, you will need to take a call for each app—whether to write or reuse an existing one.

It might sound easier to install and use a readily available app. However, it not as simple as it sounds. Let's take a look at some third-party authentication apps for our project, and list the reasons why we didn't use them for SuperBook at the time of writing:

• Over-engineered for our needs: We felt that python-social-auth with support for any social login was unnecessary
• Too specific: Using django-facebook would mean tying our authentication to that provided by a specific website
• Python dependencies: One of the requirements of django-allauth is python-openid, which is not actively maintained or unapproved
• Non-Python dependencies: Some packages might have non-Python dependencies, such as Redis or Node.js, which have deployment overheads
• Not reusable: Many of our own apps were not used because they were not very easy to reuse or were not written to be reusable

None of these packages are bad. They just don't meet our needs for now. They might be useful for a different project. In our case, the built-in Django auth app was good enough.
On the other hand, you might prefer to use a third-party app for some of the following reasons:

• Too hard to get right􏰕 Do your 􏰀odel's instances need to for􏰀 a tree? Use django-mptt for a database􏰉efficient i􏰀ple􏰀entation
• Best or recommended app for the job: This changes over time but packages such as django-redis are the most recommended for their use case
• Missing batteries: Many feel that packages such as django-model-utils and django-extensions should have been part of the framework
• Minimal dependencies: This is always good in my book

S􏰊o, should you reuse apps and save ti􏰀e or write a new custo􏰀 app? I would recommend that you try a third-party app in a sandbox. If you are an intermediate Django developer, then the next section will tell you how to try packages in a sandbox.

我的app沙箱

From time to time, you will come across several blog posts listing the "must-have Django packages". However, the best way to decide whether a package is appropriate for your project is Prototyping.

Even if you have created a Python virtual environment for development, trying all these packages and later discarding them can litter your environment. So, I usually end up creating a separate virtual environment named "sandbox" purely for trying such apps. Then, I build a small project to understand how easy it is to use.

Later, if I am happy with my test drive of the app, I create a branch in my project using a version control tool such as Git to integrate the app. Then, I continue with coding and running tests in the branch until the necessary features are added. Finally, this branch will be reviewed and merged back to the mainline (sometimes called master) branch.

它是由哪个包组成的?

为了阐明过程,我们的SuperBook项目可以粗略地分解为下列app(没有全部列出):

Authentication(内建django.auth): 这个app处理用户注册,登录,和登出。
Accounts(定制): 这个app提偶那个额外的用户账户信息。
Posts(定制): 这个app提供发表和回复功能
Pows(定制): 这个app跟踪有多少“碰”(支持或者喜欢)
Boostrap forms(crispy-forms): 这个app处理表单布局和风格

这里,一个app已经被标记为从零(标记为“定制”)构建,或者我们要使用的第三方Django应用。随着,项目的发展,这些选项或许改变。不过,这对一个新手足够了。

在开始项目之前

While preparing a development environment, make sure that you have the following in place:

• A fresh Python virtual environment: Python 3 includes the venv module or you can install virtualenv. Both of them prevent polluting your global Python library.
• Version control: Always use a version control tool such as Git or Mercurial. They are life savers. You can also 􏰀ake changes 􏰀uch 􏰀ore confidently and fearlessly.
• Choose a project template: Django's default project template is not the only option. Based on your needs try others such as twoscoops (https://github.com/twoscoops/django-twoscoops-project) or edge (https://github.com/arocks/edge).
• Deployment pipeline: I usually worry about this a bit later, but having an easy deployment process helps to show early progress. I prefer Fabric or Ansible.

SuperBook-你的任务,你应该选择去接受

This book believes in a practical and pragmatic approach of demonstrating Django design patterns and the best practices through examples. For consistency, all our examples will be about building a social network project called SuperBook.

SuperBook focusses exclusively on the niche and often neglected market segment of people with exceptional super powers. You are one of the developers in a tea􏰀 comprised of other developers, web designers, a marketing manager, and a project manager.

The project will be built in the latest version of Python (Version 3.4) and Django (Version 1.7) at the time of writing. Since the choice of Python 3 can be a contentious topic, it deserves a fuller explanation.

为什么选Python 3?

While the development of Python 􏰄 started in 􏰁􏰂􏰂􏰅, its first release, Python 􏰄.􏰂, was released on December 3, 2008. The main reasons for a backward incompatible version were—switching to Unicode for all strings, increased use of iterators, cleanup of deprecated features such as old-style classes, and some new syntactic additions such as the nonlocal statement.

The reaction to Python 3 in the Django community was rather mixed. Even though the language changes between Version 2 and 3 were small (and over time, reduced), porting the entire Django codebase was a significant 􏰀igration effort.

􏰔n February 􏰃􏰄, Django 􏰃.􏰜 beca􏰀e the first version to support Python 􏰄. Developers have clarified that, in future, Django will be written for Python 􏰄 with an ai􏰀 to be backward compatible to Python 2.

For this book, Python 3 was ideal for the following reasons:

• Better syntax􏰕 This fixes a lot of ugly syntaxes, such as izip, xrange, and __unicode__, with the cleaner and more straightforward zip, range, and __str__.
• Sufficient third-party support: Of the top 200 third-party libraries, more than 80 percent have Python 3 support.
• No legacy code: We are creating a new project, rather than dealing with legacy code that needs to support an older version.
• Default in modern platforms: This is already the default Python interpreter in Arch Linux. Ubuntu and Fedora plan to complete the switch in a future release.
• It is easy: From a Django development point of view, there are very few changes, and they can all be learnt in a few minutes.

The last point is important. Even if you are using Python 2, this book will serve you fine. Read 􏰆ppendix 􏰆 to understand the changes. You will need to make only minimal adjustments to backport the example code.

开始项目

本节是SuperBook项目的安装说明,它包含在本书中使用的所有代码实例。要为最新的安装注释检查项目的README文件。推荐你创建一个新的目录,superbook,首先包含虚拟环境,项目源码。

Ideally, every Django project should be in its own separate virtual environment. This makes it easy to install, update, and delete packages without affecting other applications. In Python 3.4, using the built-in venv module is recommended since it also installs pip by default:

$ python3 -m venv sbenv
$ source sbenv/bin/activate
$ export PATH="`pwd`/sbenv/local/bin:$PATH"

These commands should work in most Unix-based operating systems. For installation instructions on other operating systems or detailed steps please refer to the README file at the 􏰒ithub repository􏰕 https://github.com/DjangoPatternsBook/ superbook. In the first line, we are invoking the Python 􏰄.􏰝 executable as python3;
do confir􏰀 if this is correct for your operating syste􏰀 and distribution.

The last export command might not be required in some cases. If running pip freeze lists your system packages rather than being empty, then you will need to enter this line.

提示

开始Django项目之前,创建一个新的虚拟环境
接着,从GitHub克隆项目例子,并安装依赖:

    $ git clone https://github.com/DjangoPatternsBook/superbook.git
    $ cd superbook
    $ pip install -r requirements.txt

如果你想看一看已完成的SuperBook网站,只要运行migrate并启动测试服务器就行了:

    $ cd final
    $ python manage.py migrate
    $ python manage.py createsuperuser
    $ python manage.py runserver  

在Django1.7中,migrate命令已经被syncdb命令取代。我们也需要明确地调用creteasuperuser命令来创建一个超级用户,这样我们就可以访问admin了。

你可以浏览http://127.0.0.1:8000或者在终端指明URL,随便玩玩这个网站。

总结

Beginners often underestimate the importance of a good requirements-gathering process. At the same time, it is important not to get bogged down with the details, because programming is inherently an exploratory process. The most successful projects spend the right amount of time preparing and planning before develop􏰀ent so that it yields the 􏰀axi􏰀u􏰀 benefits.

We discussed many aspects of designing an application, such as creating interactive mockups or dividing it into reusable components called apps. We also discussed the steps to set up SuperBook, our example project.

In the next chapter, we will take a look at each component of Django in detail and learn the design patterns and best practices around them.


© Creative Commons BY-NC-ND 3.0 | 我要订阅 | 我要捐助

第七章-表单

第七章-表单


这一章我们会讨论一下话题:

• 表单的工作流程
• 不可信的输入
• 表单处理类视图
• 表单与CRUD视图

我们把Django表单放到一边,来讨论下常规情况下的表单是个什么样子。表单不仅长,而且有着多个需要填充的无趣的页面。可以说表单无所不在。我们每天都用到它。表单支撑了谷歌搜索框到脸书的点赞按钮这所有的一切。

Django把使用表单时产生的验证和描述这类的大量繁重工作给抽象了。它也实现了多种的安全问题的最佳实践。可是,表单在处理自身多个状态之一时也是令人困惑的起因。

表单的工作原理

表单理解起来比较困难,因为它同不止一个请求-响应循环交互。最简单的场景是,你需要展示一个空表单,然后用户来正确地填充和提交表单。另外一种情况是它们输入一些无效的数据,表单需要重复的提交知道整个表单有效为止。

因此,表单表现出多种状态:

    • **空表单:**在Django中此表单称为未绑定表单
    • **已填充表单:**Django中该表单称为已绑定表单
    • **有错误的已提交表单:**该表单称做已绑定表单,但不是有效表单
    • **没有错误的已提交表单:**该表单称做已绑定且有效的表单

注意用户永远不会见到表单的最后状态。他们不必如此。提交的有效表单应当把用户带到表单提交成功之后的页面。

Django中的表单

通过总结它们到一个层次,Django的form类每个字段的状态,以及表单自身。表单拥有两个重要的状态属性,一如下面所示:

    • *is_bound*: 如果返回值为假,则它是一个未绑定的表单,即,新的空表单,或者默认的字段值。如果返回值为真,表单被绑定,即,至少有一个用户设置的字段。

    • *is_valid()*: 如果返回值为真,已绑定表单中的所有字段都拥有有效的数据。如果返回假,至少有一个字段中存在一部分无效数据,或者表单未被绑定。

举例来说,想象一下你需要一个接受用户名字和年龄的简单表单。这个类可以这样来定义:

    # forms.py
    from django import forms


    class PersonDetailsForm(forms.Form):
       name = forms.CharField(max_length=100)
       age = forms.IntegerField()

该类可以以绑定或者不绑定方式来初始化,一如下面代码所示:

>>> f = PersonDetailsForm()
>>> print(f.as_p())
<p><label for="id_name">Name:</label> <input id="id_name" maxlength="100"
name="name" type="text" /></p>
<p><label for="id_age">Age:</label> <input id="id_age" name="age"
type="number" /></p>
>>> f.is_bound
False
>>> g = PersonDetailsForm({"name": "Blitz", "age": "30"})
>>> print(g.as_p())
<p><label for="id_name">Name:</label> <input id="id_name" maxlength="100"
name="name" type="text" value="Blitz" /></p>
<p><label for="id_age">Age:</label> <input id="id_age" name="age"
type="number" value="30" /></p>
>>> g.is_bound
 True

要注意HTML是如何表现改变以包括它们中已绑定数据的值属性。

表单可以只在你创建表单对象时才被绑定,即,在构造器中。用户如何在类字典对象的最后面输入每个表单字段的值?

要想解决这个问题,你需要理解用户是如何与表单交互的。在下面的的图表中,用户打开用户账户表单,首先是正确地填充,并提交它,然后用有效信息重新提交表单:

2015-06-15 21 44 02

如前面的表单所示,当用户提交表单时,在request.POST(它是一个QueryDict的实例)内部所有可调用的视图获取到全部的表单数据。表单使用类字典对象——以这种方式引用是因为它的行为类似于字典,来初始,并拥有一点额外的功能。

表单可以通过两种不同的方式来定义以发送数据表单:GET或者POST。表单使用METHOD=“GET”定义以发送以URL编码的表单数据,例如,当你提交谷歌搜索时,URL取得表单输入,即,搜索字符串显式地嵌入,比如?q=Cat+Pictures就是如此。GET方法用来幂等表单,它不会对世界状态做出任何的最新改变。(不要太过于迂腐,多次处理有同样的效果就像一次处理)。大多数情况下,这意味着它只在重新取回数据时被用到。

不过,不计其数的表单都是使用METHOD=“POST”来定义的。这样,表单数据会一直发送HTTP请求的主体部分,而且它们对于用户来说是不可见的。它们被用于任何涉及到边际效应的事情,比如存储或者更新数据。

视你所定义表单类型的不同,当用户提交表单时,视图会重新取回request.GET或者request.POST中的表单数据。如同早前咱么提到的那样,它们中的哪一个都类似于字典。因此,你可以传递它到表单类构造器以获取绑定的form对象。

注释

The Breach
Steve was curled up and snoring heavily in his large three-seater couch. For the last few weeks, he had been spending more than 12 hours at the office, and tonight was no exception. His phone lying on the carpet beeped. 􏰅t first, he said so􏰀ething incoherently, still deep in sleep. Then, it beeped again and again, in increasing urgency.

By the fifth beep, 􏰆teve awoke with a start. He frantically searched all over his couch, and finally located his phone. The screen showed a brightly colored bar chart. Every bar seemed to touch the high line except one. He pulled out his laptop and logged into the SuperBook server. The site was up and none of the logs indicated any unusual activity. However, the external services didn't look that good.

The phone at the other end seemed to ring for eternity until a croaky voice answered, 􏰇Hello, 􏰆teve?􏰇 Half an hour later, 􏰈acob was able to 􏰄ero down the proble􏰀 to an unresponsive superhero verification service. 􏰇Isn't that running on 􏰆auron?􏰇 asked 􏰆teve. There was a brief hesitation. "I am afraid so," replied Jacob.

Steve had a sinking feeling at the pit of his stomach. Sauron, a 􏰀ainfra􏰀e application, was their first line of defense against cyber-attacks and other kinds of possible attack. It was three in the morning when he alerted the mission control team. Jacob kept chatting with him the whole time. He was running every available diagnostic tool. There was no sign of any security breach.

Steve tried to calm him down. He reassured him that perhaps it was a temporary overload and he should get some rest. However, he knew that Jacob wouldn't stop until he found what's wrong. He also knew that it was not typical of Sauron to have a temporary overload. Feeling extremely exhausted, he slipped back to sleep.

Next 􏰀morning, as 􏰆steve hurried to his office building holding a bagel, he heard a deafening roar. He turned and looked up to see a massive spaceship looming towards him. Instinctively, he ducked behind a hedge. On the other side, he could hear several heavy metallic objects clanging onto the ground. Just then his cell phone rang. It was Jacob. Something had moved closer to him. As Steve looked up, he saw a nearly 10-foot-tall robot, colored orange and black, pointing what looked like a weapon directly down at him.

His phone was still ringing. He darted out into the open barely missing the sputtering shower of bullets around him. He took the call. "Hey Steve, guess what, I found out what actually happened." "I am dying to know," Steve quipped.

"Remember, we had used UserHoller's form widget to collect customer feedback? 􏰅pparently, their data was not that clean. I 􏰀ean several serious exploits. Hey, there is a lot of background noise. Is that the T􏰊?􏰇 Steve dived towards a large sign that said "Safe Assembly Point". "Just ignore that. Tell me what happened," he screamed.

"Okay. So, when our admin opened their feedback page, his laptop must have gotten infected. The worm could reach other systems he has access to, specifically, 􏰆auron. I 􏰀ust say 􏰈acob, this is a very targeted attack. Someone who knows our security system quite well has designed this. I have a feeling something scary is coming our way."

Across the lawn, a robot picked up an SUV and hurled it towards Steve. He raised his hands and shut his eyes. The spinning mass of metal froze a few feet above hi􏰀. 􏰇I􏰀portant call?􏰇 asked Hexa as she dropped the car. 􏰇Yeah, please get 􏰀e out of here,􏰇 􏰆teve begged.

为什么数据需要清理

终究你还是需要从表单获取“干净的数据”。这是否意味着用户输入的值是否是不干净的呢?是的,这里有两个理由。

首先来自外部世界的任何东西都不应该一开始就被信任。恶意用户可以对表单输入所有类别的探测利用,以此破坏网站的安全。因此,任何的表单数据在使用前都必须被净化。

提示

Best Practice
任何时候都不能信任用户的输入。

􏰆第二,request.POST或者request.GET中的字段值只是字符串而已。即使表单字段定义为整数(比如说,年龄)或者日期(比如说,生日),浏览器也是把这些字段以字符串的形式发送到视图。不可避免的是在加以使用之前你要把它们转换到适当的Python数据类型。form类在进行清理时会自动地达成约定。

我们来看看实际的例子:

    >>> fill = {"name": "Blitz", "age": "30"}
    >>> g = PersonDetailsForm(fill)
    >>> g.is_valid()
        True
    >>> g.cleaned_data
        {'age': 30, 'name': 'Blitz'}
    >>> type(g.cleaned_data["age"])
        int

年龄值作为字符串来传递(或许来自request.POST)到表单类。验证之后,干净数据包含整数形式的年龄。这完全符合用户的期望。表单试图抽象出字符串的传递,以及对用户给出可以使用的干净的Python数据类型的事实。

显示表单

Django表单也能够帮助你生成表现表单的HTML。它们支持膻中不同的表现形式:as_p(作为段落标签),as_ul(显示为无序列表项),以及as_table(意料之中,显示为表格)。

模板代码生成HTML代码,浏览器渲染这些表现已经总结为下表:

图片:略

􏰉注意HTML表现仅给出了表单字段。这样做可以在一个单独的HTML表单中轻松地包含多个Django表单。可是,这样做也意味着模板设计者必须有点儿公式化来写每个表单,一如下面代码所示:

<form method="post">
     {% csrf_token %}
     <table>{{ form.as_table }}</table>
     <input type="submit" value="Submit" />
</form>

注意,为了使HTML完整的表现,你需要添加form标签周添加CSRF令牌环,table或者ul标签,以及submit按钮。

是使用crisp的时候了

在模板中写这么多公式化的表单是很无聊的事情。django-crispy-forms包可以写非长干脆利落的来写表单模板。它把所有表现和布局都放到了Django表单自身。因此,你可以写更多的Python代码,更少的HTML。

下面的表展示了csrispy表单的模板标签生成更加完整的表单,而且外观更加的接近原生的Bootstrap风格:

Template Code Output in Browser
{% crispy_form} <from method="post"><input type='hidden' name='csrfmiddlewaretoken' value='...'/><div id='div_id_name' class='form-group'><label for='id_name' class="control-lable requiredField">Name<span class='asteriskField'>*</span></label><div class="controls"><input class='textinput textInput form-control ' id='id_name' maxlength="100" name='name' type="text" /></div></from>(为了简洁而删去余下的HTML)

􏰆那么,你是如何实现更加干脆利落的表单的?你需要安装django-crispy-forms包并将它加入到INSTALLED_APPS。如果你使用Bootstrap 3,那么你需要在设置中引用:

CRISPY_TEMPLATE_PACK = "bootstrap3"

表单初始化需要引用类型FormHelper的辅助属性。下面的代码以最小化设计,并使用默认的布局:

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit


   class PersonDetailsForm(forms.Form):
       name = forms.CharField(max_length=100)
       age = forms.IntegerField()

       def __init__(self, *args, **kwargs):
           super().__init__(*args, **kwargs)
           self.helper = FormHelper(self)
           self.helper.layout.append(Submit('submit', 'Submit'))

理解CSRF

你也一定注意到了在表单模板中叫做CSRF的东西。它用来干什么的?它是一种应对针对表单的跨站请求伪造(CSRF)的保护机制。

它通过注入服务端生成的随机的叫做CSRF令牌的字符工作,这个字符串对用户的session来说是唯一的。每次表单提交时,都有一个包含此令牌的隐藏字段。这个令牌确保表单是由用户生成而且是来自原始站点的,而不是由攻击者使用类似字段创建的假冒表单。

在表单使用GET方法时并不推荐CSRF令牌,因为GET行为不应该改变服务器状态。此外,表单通过GET提交表单会在URL中暴露CSRF令牌。因此URL在登录或者并行嗅探时具有更高的风险,当使用POST方式最好在表单中使用CSRF。

表单处理类视图

基本上,我们可以通过子类化类视图来处理表单:

    class ClassBasedFormView(generic.View):
       template_name = 'form.html'
       def get(self, request):
           form = PersonDetailsForm()
           return render(request, self.template_name, {'form': form})
       def post(self, request):
           form = PersonDetailsForm(request.POST)
           if form.is_valid():
               # 成功的话,我就可以使用了form.cleaned_data。
               return redirect('success')
           else:
               # 无效,重新显示含有错误高亮的表单
               return render(request, self.template_name,
                             {'form': form})

上面的代码与我们之前的见到的序列图表相比较。三种应用场景已经被分别处理。

每一个表单都如期望的那样遵守Post/Redirect/Get(PRG)模式。如果提交的表单发现是无效的,它必须发起一个重定向。这能够阻止重复表单的提交。

不过,这样做是并不十分符合DRY原则的编写。表单类名称和模板名称属性都已经重复了。使用FormView这样的通用类视图能够表单处理中的冗余部分。下面的代码会给你带来和前面一样的功能而且还少了几行代码:

from django.core.urlresolvers import reverse_lazy


class GenericFormView(generic.FormView):
    template_name = 'form.html'
    form_class = PersonDetailsForm
    success_url = reverse_lazy("success")

这个例子中我们需要使用reverse_lazy,因为URL模式在视图文件导入时并没有被载入。

表单模式

我们来看一看使用表单时会见到的一些常用模式。

模式-动态表单的生成

问题:自动地添加表单字段或者改变已经声明的表单字段。

解决方案:在表单初始化的时候添加或者改变字段。

问题细节

表单通常以一种将拥有的字段列为类字段的声明方式。不过,有时候我们不能提前知道数量,或者这些字段的类型。这就要求表单能够动态地生成。该模式有时称为动态表单或者运行时的表单生成

想象有一个旅客航班登录系统,它允许经济舱机票改签为头等舱。假如头等舱座席有剩余,那么在用户想要乘坐头等舱是就需要有一个额外的选项。不过,这个额外的字段不能够公开,因为它对于全部用户来说是不可见的。这样的动态表单就可以通过该模式来处理。

解决方案细节

每一个表单的实例都有一个叫做字段的fields,它是一个拥有全部字段的字典。可以在运行时对它作出修改。添加或者改变字段可以在表单初始化时完成。

例如,如果我们添加一个到用户详情表单的复选框,只要在表单初始化时命名为“upgrade”的关键字参数为真,那么我们就可以以如下代码来实现它:

class PersonDetailsForm(forms.Form):
    name = forms.CharField(max_length=100)
    age = forms.IntegerField()
    def __init__(self, *args, **kwargs):
       upgrade = kwargs.pop("upgrade", False)
       super().__init__(*args, **kwargs)
       # Show first class option? 显示头等舱选项?
       if upgrade:
            self.fields["first_class"] = forms.BooleanField(label="Fly First Class?")

现在,我们只需要传递关键字参数PersonDetailsForm(upgrade=True)以产生一个额外的布尔输入字段(复选框)。

注释

注意,最新引入的关键字参数在我们调用super以避免unexpected keyword错误已经被移除,或者去掉。

如果我们对这个例子使用FormView类,那么我们需要通过重写视图类的get_form_kwargs方法来传递关键字参数,一如下面代码所示:

class PersonDetailsEdit(generic.FormView):
    ...
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["upgrade"] = True
        return kwargs

这个模式可以用来在运行时改变任意一个字段的属性,比如它的部件或者辅助文本。它也能够用于模型表单。

在很多情况下,外观所需的动态表单可以通过使用Django表单集合来解决。在页面中当表单需要重复时就要用到它们了。一个典型的表单集合用法是在设计数据的网格视图时一行接一行的添加元素。因此,你不需要使用任意数量的行来创建一个动态表单。你只需依照行来创建表单,使用formset_factory函数来创建多个行。

􏰎􏰏 模式-用户表单

问题:表单需要根据已经登录的用户来进行定制。

解决方案:传递已登录用户作为关键字参数到表单的构造器。

问题细节

表单可以按用户以不同的形式来表现。某些用户或许不需要填充全部字段,而其他人就需要添加额外的信息。某些情况下,你或许需要对用户资格做些检查,比如,验证他们是否为一个组的成员,以便搞清楚如何构建表单。

解决方案细节

你一定注意到了,你可以使用动态表单生成模式中给出的解决方案来解决这个问题。你只需要将request.user作为一个关键字参数传递到表单。然而,为了简洁和更富于重复使用的解决方案,我们也可以使用django-braces包中的mixin。

如前面的例子所示,我们需要对用户展示一个额外的复选框。可是,这仅在用户是VIP组成员时才会被显示。让我们来看看PersonDetailsForm是如何使用django-braces的表单mixinUserKwargModelFormMixin来简化的:

from braces.forms import UserKwargModelFormMixin


class PersonDetailsForm(UserKwargModelFormMixin, forms.Form):
    ...
    def __init__(self, *args, **kwargs):
       super().__init__(*args, **kwargs)
       # Are you a member of the VIP group?
       # 你是VIP组的成员吗?
       if self.user.groups.filter(name="VIP").exists():
           self.fields["first_class"] = forms.BooleanField(label="Fly First Class?")

注意self.user如何通过mixin去掉用户关键字参数被自动地设为可用。

对应到表单mixin,有一个成为UserFormKwargsMixin的视图mixin,它需要添加到视图,使用LoginRequiredMixin以确保只有已经登录的用户可以访问该视图:

   class VIPCheckFormView(LoginRequiredMixin, UserFormKwargsMixin,
   generic.FormView):
      form_class = PersonDetailsForm
       ...

现在用户参数自动地传递到表单PersonDetailsForm

django-braces中检查其他的表单mixin,比如拿FormValidMessageMixin来说,它就是常见的表单使用模式的一个现成的方案。

模式-一个视图的多个表单行为

问题:在一个独立的视图或者页面中处理多个表单行为。

解决方案:表单可以使用独立的视图来处理表单提交,或者识别基于提交按钮名称的表单。

问题细节

Django使合并多个拥有相同行为的表单相当地简单,例如,单独的一个提交按钮。不过,大多数的页面需要在同样的页面上显示多个行为。例如,你或许想要用户在同一页面的两个不同表单中订阅或取消订阅一个新闻简报。

不过,Django的FormView被设计成只处理一个表单对应一个视图的场景。很多的其他通用类视图也共享该假定。

解决方案细节

处理多个表单有两个办法:分离视图和独立视图。首先我们来看下第一个方法。

独立的行为分离视图

依据视图的行为为每个表单指定不同的视图,是一个相当简单的办法。例如,订阅和取消订阅表单。刚好有两个独立的视图类来处理他们各自表单的POST方法。

独立的相同视图

可能你发现了分割视图来处理表单是没有必要的,抑或发现了使用一个公共视图来处理逻辑上相关连的表单为更加简洁明了。

当对多个表单使用相同的视图类时,其挑战在于标识哪个表单来处理POST行为。这里,我们利用了事实上的优势,提交按钮的名称和值同时被提交了。假如提交按钮在表单中命名唯一,那么处理时表单就可以被标识出来。

这里,我们定义一个使用crispy表单的订阅器,这样我们就可以的命名提交按钮了:

   class SubscribeForm(forms.Form):
       email = forms.EmailField()

       def __init__(self, *args, **kwargs):
           super().__init__(*args, **kwargs)
           self.helper = FormHelper(self)
           self.helper.layout.append(Submit('subscribe_butn', 'Subscribe'))

取消订阅表单类UnSubscribeForm以完全相同的方式来定义(因此,这里我们就省略不写出来了),除了Submit按钮被命名为unscribe_butn之外。

因为FormView被设计成单视图,我们会使用一个简单的类视图,即,TemplageView,来做为视图的基类。我们来看一看视图定义以及get方法:

    from .forms import SubscribeForm, UnSubscribeForm

    class NewsletterView(generic.TemplateView):
       subcribe_form_class = SubscribeForm
       unsubcribe_form_class = UnSubscribeForm
       template_name = "newsletter.html"

       def get(self, request, *args, **kwargs):
           kwargs.setdefault("subscribe_form", self.subcribe_form_class())
           kwargs.setdefault("unsubscribe_form", self.unsubcribe_form_class())
           return super().get(request, *args, **kwargs)

TemplateView类的关键字参数可以方便地插入进模板上下文。我们仅在表单实例不存在时,利用setdefault字典方法的帮助来创建其中一个表单的实例。我们很快就可以看到为什么要这样做。

接下来,我们来看看POST方法,它处理了表单的提交:

        def post(self, request, *args, **kwargs):
           form_args = {
               'data': self.request.POST,
               'files': self.request.FILES,
           }

           if "subscribe_butn" in request.POST:
               form = self.subcribe_form_class(**form_args)
               if not form.is_valid():
                   return self.get(request,
                                      subscribe_form=form)
               return redirect("success_form1")
           elif "unsubscribe_butn" in request.POST:
               form = self.unsubcribe_form_class(**form_args)
               if not form.is_valid():
                   return self.get(request,
                                      unsubscribe_form=form)
               return redirect("success_form2")
           return super().get(request)

首先,表单的关键字参数,比如数据和文件,就是在form_args字典中产生的。接下来,第一个表单的Submit按钮的存在是用来检查request.POST。假如发现了按钮的名称,那么第一个表单就被初始化。

如果表单验证失败,那么响应通过第一个表单实例的GET方法被创建的方法就会返回。同样地,我们查找第二个表单的提交按钮以检查第二个表单是否被提交。

相同视图中的相同表单的实例可以通过表单前缀以相同方式来实现。你可以使用SubscribeForm(prefix="offers")这样的前缀参数来实例化一个表单。比如实例利用给出的参数作为前缀加入到所有的表单字段,实际上当作一个表单的命名空间来使用。

模式-CRUD视图

问题:公式化的对模型编写CRUD接口是在重复相同的事。

解决方案:使用类的通用视图来编辑视图。

问题细节

在大多数的web应用中,大约百分之80的时间被用来写,创建,读取,更新以及删除(CRUD)数据库的接口。例如,基本上,Twitter就涉及到了创建和读取其他用户的推文。这里,推文是可以被维护和存储的数据库对象。

要是从零开始写这样的接口实在是乏味至极。如果CRUD接口可以自动地从模型类创建,那么这个模式可以轻松地管理。

解决方案细节

Django利用了一个四个通用类视图的组简化了创建CRUD视图的过程。如下,它们可以被映射到其自身像对应的操作:

    • CreateView: 该视图显示一个空白表单以创建一个新的对象。
    • DetailView: 该视图通过读取数据库来展示一个对象的细节。
    • UpdateView: 该视图被允许通过一个预先生成的表单来更新一个对象的细节。
    • DeleteView􏰂: 该视图像是一个确认页面,并准许删除对象。

让我们来看一个简单的例子。我们拥有一个包含重要日期的模型,它关系到使用网站的每一个用户的利益。我们需要构建简单的CRUD接口,这样任何人都可以查看,修改这些日期。我们来看下Importantdate模型的内容:

# models.py
   class ImportantDate(models.Model):
       date = models.DateField()
       desc = models.CharField(max_length=100)
       def get_absolute_url(self):
           return reverse('impdate_detail', args=[str(self.pk)])

get_absolute_url()方法被CreateViewUpdateView类在对象成功创建或者更新之后使用。它已经被路由到了对象的DetailView

这些CRUD视图足够的简单,因此它们不解自明的,一如以下代码所示:

    # views.py
   from django.core.urlresolvers import reverse_lazy
   from . import forms


   class ImpDateDetail(generic.DetailView):
       model = models.ImportantDate

   class ImpDateCreate(generic.CreateView):
       model = models.ImportantDate
       form_class = forms.ImportantDateForm

   class ImpDateUpdate(generic.UpdateView):
       model = models.ImportantDate
       form_class = forms.ImportantDateForm

   class ImpDateDelete(generic.DeleteView):
       model = models.ImportantDate
       success_url = reverse_lazy("impdate_list")

这些通用视图中,模型类只是强制成员被引用。不过,在DeleteView的情形下,success_url函数需要很好地的应用。这是因为get_absolute_url删除之后再也不能够用来找到在什么地方重定向用户。

定义form_class属性并非是强制的。如果它被省略,与ModelForm方法相当的模型会被创建。不过,我们想要创建自己的模型表单以利用crispy表单,一如下面代码所示:

   # forms.py
   from django import forms
   from . import models
   from crispy_forms.helper import FormHelper
   from crispy_forms.layout import Submit


   class ImportantDateForm(forms.ModelForm):
       class Meta:
           model = models.ImportantDate
           fields = ["date", "desc"]
       def __init__(self, *args, **kwargs):
           super().__init__(*args, **kwargs)
           self.helper = FormHelper(self)
           self.helper.layout.append(Submit('save', 'Save'))

要感谢crispy表单,我们在模板中需要非常少的HTML装饰以构建这些CRUD表单。

􏰉##### 注释
注意明确地引用ModelForm方法的字段就是最佳实践,在未来的发行版中这也将成为强制规定。

默认,模板的路径基于视图类和模型的名称。为了简洁起见,此处我们省略了模板的源码。注意我们可以对CreateViewUpdateView使用相同的表单。

最后,我们来看看urls.py,这里所有东西都连接起来了:

url(r'^impdates/create/$',
       pviews.ImpDateCreate.as_view(), name="impdate_create"),
   url(r'^impdates/(?P<pk>\d+)/$',
       pviews.ImpDateDetail.as_view(), name="impdate_detail"),
   url(r'^impdates/(?P<pk>\d+)/update/$',
       pviews.ImpDateUpdate.as_view(), name="impdate_update"),
   url(r'^impdates/(?P<pk>\d+)/delete/$',
       pviews.ImpDateDelete.as_view(), name="impdate_delete"),

Django的通用是我们开始为模型创建CRUD视图的一种了不起的方式。仅需少少几行代码,你就可以获得经过良好测试的模型表单和视图,而不用自己来做重复乏味的工作。

总结

在这一章,我们见过了web表单如何被创建,以及在Django中如何利用表单类将它们抽象。我们也即拿过了在使用表单时,利用多种技术和模式去节省时间。

在下一章,我们来看一看在使用旧版本的Django代码库时用到的系统化的方法,以及当我们碰到用户需要时如何的加强它。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.