<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>年华转瞬 &#187; Django开发</title>
	<atom:link href="http://blog.xiaket.org/category/django/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.xiaket.org</link>
	<description>xiaket 的网志</description>
	<lastBuildDate>Sat, 21 Aug 2010 02:31:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>基于Django/jQuery的多文件上传实现 — 第五天</title>
		<link>http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/</link>
		<comments>http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/#comments</comments>
		<pubDate>Sat, 16 Jan 2010 16:50:27 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Web开发]]></category>
		<category><![CDATA[Uploadify]]></category>
		<category><![CDATA[编码]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=271</guid>
		<description><![CDATA[隔了几天, 接着做这个项目.

首先是上传, 前几天js, 视图, 文件句柄这块的代码比较乱, 今天首先整理了下. 保证代码中只有最简单的逻辑, 包括文件上传前js提示上传了什么文件, 视图函数里不保存这个文件, 而是直接返回字符串.

今天碰到的第一个问题是上传后js里面的onComplete没有执行. 我首先猜测是视图函数处理有错误. 看Apache的log发现返回的状态值是200, 这证明swf已经post过来数据而且拿到正确的返回值了. 进一步的debug发现视图函数的逻辑已经全部正确地处理完了. 我猜测是由于我的视图函数太简单, 没有读取和处理上传的数据, 导致swf客户端有问题, 于是用python的logging模块记录了request的具体信息. 用Django文件处理的样例函数保存了上传的数据. 测试发现上传的数据能够成功地保存, 因此服务器端是没有问题的. 这个应该是比较容易理解的, 因为无论服务器端是否处理上传的数据, 这个数据在swf客户端的眼里已经上传到服务器了. 那么我们接下来应该做的事情就比较简单了: 处理客户端js脚本的函数调用, 看到底是什么地方出了问题.

<span class="readmore"><a href="http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/" title="基于Django/jQuery的多文件上传实现 — 第五天">阅读全文——共1455字</a></span>]]></description>
			<content:encoded><![CDATA[<p>隔了几天, 接着做这个项目.</p>
<p>首先是上传, 前几天js, 视图, 文件句柄这块的代码比较乱, 今天首先整理了下. 保证代码中只有最简单的逻辑, 包括文件上传前js提示上传了什么文件, 视图函数里不保存这个文件, 而是直接返回字符串.</p>
<p>今天碰到的第一个问题是上传后js里面的onComplete没有执行. 我首先猜测是视图函数处理有错误. 看Apache的log发现返回的状态值是200, 这证明swf已经post过来数据而且拿到正确的返回值了. 进一步的debug发现视图函数的逻辑已经全部正确地处理完了. 我猜测是由于我的视图函数太简单, 没有读取和处理上传的数据, 导致swf客户端有问题, 于是用python的logging模块记录了request的具体信息. 用Django文件处理的样例函数保存了上传的数据. 测试发现上传的数据能够成功地保存, 因此服务器端是没有问题的. 这个应该是比较容易理解的, 因为无论服务器端是否处理上传的数据, 这个数据在swf客户端的眼里已经上传到服务器了. 那么我们接下来应该做的事情就比较简单了: 处理客户端js脚本的函数调用, 看到底是什么地方出了问题.</p>
<p>要debug, 首先将这个函数的内部逻辑减到最少, 我写了一个简单的函数, 里面只有一个alert语句, 然后在uploadify初始化的调用中, 写下:</p>
<pre class="brush: jscript;">
        'onSelect'       : function (event, ID, fileObj){ onSelect(ID, fileObj);},
        //'onComplete'     : function (event, ID, fileObj, response, data){ onComplete(queueID, fileObj,          response, data);},
        'onComplete'     : function (event){ simplealert();},
</pre>
<p>点击, 执行上传, 发现alert语句能够正确执行.</p>
<p>我特意在视图函数里面加了一个sleep. 测试发现这个alert语句是在视图函数处理完(sleep完)之后才执行的, 因此, 目前我们的代码都还是按照我们的思路在运行的.</p>
<p>接下来的debug让我发现我在前面犯了一个很低级的错误, 我上次为了简单将有些函数调用里的queueID简写成了ID, 而今天在我写onComplete这个函数的时候又使用了旧的queueID这个名字. 真囧. 修改好以后onComplete就能在正常的视图函数返回后显示服务器端返回的内容了.</p>
<p>接下来测试是否能正常保存一个中文文件. 我前面是把post过来的内容直接存到一个固定的文件里面去. 而现在我尝试根据客户端传过来的文件创建一个新文件. 当上传一个英文名的文件时, 系统运行是正常的, 当上传一个中文名的文件时, 却有问题, 日志记录下的错误是UnicodeEncodeError, 又遇到编码问题了.</p>
<p>现在我的目的很简单, 就是上传的文件能够全部放到/home/apache/bookshop/media/temp/这个文件夹下. 几番尝试后, 发现适当地encode所涉及到的字符串就能解决中文文件上传的编码问题了:</p>
<pre class="brush: python;">
    name = file.name.encode(&quot;utf8&quot;)
    dir = u'/home/apache/bookshop/media/temp/'.encode(&quot;utf8&quot;)
    filename = dir + name
    dest = open(filename, 'wb+')
</pre>
<p>下次该考虑进度条相关的逻辑了~  今天已经晚了, 就这样吧~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>一天idea散记</title>
		<link>http://blog.xiaket.org/2009/12/31/todays-ideas/</link>
		<comments>http://blog.xiaket.org/2009/12/31/todays-ideas/#comments</comments>
		<pubDate>Wed, 30 Dec 2009 17:04:56 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[其它]]></category>
		<category><![CDATA[django-admin]]></category>
		<category><![CDATA[django-orm]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=226</guid>
		<description><![CDATA[Django相关

问: 一个models.py文件里面定义了一群模型(不知道多少个), 我想要得到一个字典, 键为模型名, 值为模型的类.

答: 这是个比较丑的办法, 暂时想不到更漂亮的了:

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/31/todays-ideas/" title="一天idea散记">阅读全文——共1549字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>Django相关</h3>
<p>问: 一个models.py文件里面定义了一群模型(不知道多少个), 我想要得到一个字典, 键为模型名, 值为模型的类.</p>
<p>答: 这是个比较丑的办法, 暂时想不到更漂亮的了:</p>
<pre class="brush: python;">
from django.db import models
import msgcenter.models
class_dict = {}
for name in dir(msgcenter.models):
    try:
        status = eval(&quot;issubclass(msgcenter.models.%s, models.Model)&quot; % name)
        class_dict[name] = eval(&quot;msgcenter.models.%s&quot; % name)
    except:
        pass
</pre>
<p>问: 一个模型里面有很多的field, 怎么拿到这个field的列表?</p>
<p>答: 这个是一个内置的属性:</p>
<pre class="brush: python;">
&gt;&gt;&gt; from msgcenter.models import Project
&gt;&gt;&gt; Project._meta.object_name
'Project'
&gt;&gt;&gt; Project._meta.get_all_field_names()
['admins',
 'fullname',
 'id',
 'popo_acount',
 'shortname']
</pre>
<p>不过这个似乎还包含了一些外键之类的东西, 有时间再细细研究下.</p>
<p>问: 我定义了一个模型, 里面有个field用来记录作者的ip, 普通视图函数还好说, 但是怎么样能够在admin界面下也能正确保存这个ip信息呢?</p>
<p>答: 几种办法. 最丑的: 改urls.py, 让我们匹配的规则在admin的规则的前面, 然后自己写视图函数, 外观做得很像很像admin界面&#8230;. 最剑走偏锋的: 用中间件, 对于到某个特殊的url的POST, 将POST的内容改掉, 添加用户的ip. 最正统的: 改admin.py里面的save_model方法&#8230;</p>
<p>第二种方法是今天和LD讨论出来的, 很有意思, 中间件这样用虽然很非主流, 但也不失为一个极端情况下的投机取巧之道. 用中间件你可以任意地修改request/response对象, 一切尽在掌握~</p>
<h3>工作相关</h3>
<p>遇到一个诡异的问题, 某个服务用户的crontab里运行的脚本ls输出和通过sudo来以这个服务用户的身份运行ls的输出结果不一样, 时间格式不同, 后来强制指定&#8211;time-style搞定&#8230;</p>
<p>今天开始写脚本处理<a href="http://mirrors.163.com/">开源镜像服务器</a>上Web服务器的日志. 今天主要解决的问题是一个很大的日志文件如何得到每个小时的访问次数. grep -c肯定是可以的, 但是效率很差. 目前想到的方法是利用日志里面的时间戳, 在python里用file对象的seek方法来处理, 而不是逐行处理, 不过这样效率到底能提高多少, 目前脚本没写出来我还没底, 目标是一个2G的文件一秒能搞定, 这样比逐行处理效率要高不少了. 直接将文件cat到/dev/null都会要几十秒~</p>
<h3>其他</h3>
<p>晚上在Windows平台下一个Debian虚拟机里面配mysqld, 怎么都起不来, 报错是无法绑定端口. 但是显然即使我起了两个mysqld也不会这么费事地排错&#8230;  后来改掉my.cnf里面的bind-address, 将127.0.0.1改为0.0.0.0就正常了&#8230;  都是VM惹的祸&#8230;   </p>
<p>新闻里广汉高铁上有人吸烟导致列车停运. 论坛上一群人骂铁道部胡乱找借口掩盖问题, 另有一群人辩护, 说, 有烟的话列车本来就应该停下来处理, 否则万一火灾就挂了. 这件事我倒有另一个看法, 目前广汉高铁的安检要求太低. 打火机是不能带上飞机的. 既然高铁的速度和飞机也差不了太多, 那么高铁上用打火机我想也应该禁止. 每秒一百米的速度, 如果着火, 绝对响起了通往天国的倒计时了&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/31/todays-ideas/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django中的中间件</title>
		<link>http://blog.xiaket.org/2009/12/27/django-middleware/</link>
		<comments>http://blog.xiaket.org/2009/12/27/django-middleware/#comments</comments>
		<pubDate>Sun, 27 Dec 2009 09:00:31 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[WSGI]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=214</guid>
		<description><![CDATA[中间件的调用

如果你看了我写的前一篇讨论Django处理HTTP请求的文章, 我在文中基本没提到中间件. 好吧, 我故意遗漏了这些, 因为中间件相对比较独立. 而且每个中间件之间也相对比较独立. 顺口说句, 松耦合就这个好处&#8230;

言归正传, 中间件的调用是在WSGIHandler被调用的开头进行的:

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/27/django-middleware/" title="Django中的中间件">阅读全文——共4376字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>中间件的调用</h3>
<p>如果你看了我写的前一篇<a href="/2009/12/25/django-httpquest-procession/">讨论Django处理HTTP请求的文章</a>, 我在文中基本没提到中间件. 好吧, 我故意遗漏了这些, 因为中间件相对比较独立. 而且每个中间件之间也相对比较独立. 顺口说句, 松耦合就这个好处&#8230;</p>
<p>言归正传, 中间件的调用是在WSGIHandler被调用的开头进行的:</p>
<pre class="brush: python;">
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        from django.conf import settings

        # Set up middleware if needed. We couldn't do this earlier, because
        # settings weren't available.
        if self._request_middleware is None:
            self.initLock.acquire()
            # Check that middleware is still uninitialised.
            if self._request_middleware is None:
                self.load_middleware()
            self.initLock.release()
</pre>
</p>
<p>显然, 关键的语句是倒数第二行里面的load_middleware方法, 我们继续跟进去看:</p>
<pre class="brush: python;">
# django/core/handlers/base.py
class BaseHandler(object):
    def load_middleware(self):
        &quot;&quot;&quot; some code omitted. &quot;&quot;&quot;
        self._view_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        request_middleware = []
        for middleware_path in settings.MIDDLEWARE_CLASSES:
            &quot;&quot;&quot; Exception handling is removed from these code. &quot;&quot;&quot;
            dot = middleware_path.rindex('.')
            mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
            mod = import_module(mw_module)
            mw_class = getattr(mod, mw_classname)
            mw_instance = mw_class()

            if hasattr(mw_instance, 'process_request'):
                request_middleware.append(mw_instance.process_request)
            if hasattr(mw_instance, 'process_view'):
                self._view_middleware.append(mw_instance.process_view)
            if hasattr(mw_instance, 'process_response'):
                self._response_middleware.insert(0, mw_instance.process_response)
            if hasattr(mw_instance, 'process_exception'):
                self._exception_middleware.insert(0, mw_instance.process_exception)

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._request_middleware = request_middleware
</pre>
<p>整个这段代码的核心意思是对于每个settings.MIDDLEWARE_CLASSES里指定的中间件, 我们探测里面有什么方法. 如果有process_request, process_view, process_response, process_exception这四个方法, 我们将其添加到BaseHandler的四个列表里去. 按照Django处理HTTP请求的顺序, 前两个列表都是在django.core.handlers.base.BaseHandler模块的get_response方法中应用的, 第三个process_response方法是在WSGIHandler的结尾处应用的, 最后一个方法是在视图函数处理HTTP请求出现异常时才被应用的.</p>
<p>用django-admin.py startproject创建的新项目里, 默认添加了下面这些中间件:</p>
<pre class="brush: python;">
MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
)
</pre>
<p>csrf这篇文章就不管了, 我们接下来分析剩余三个中间件到底执行了哪些操作. 我们主要分析process_request和process_response这两个字典.</p>
<h3>process_request</h3>
<p>CommonMiddleware里面没啥新鲜的东西, 主要做了三件事情:</p>
<ul>
<li>禁止某些User-Agent的访问(我瞬间很邪恶地想起可以禁止IE的访问&#8230;).</li>
<li>URL重写, 在URL后面添加/和在前面添加www, 这个是可以配置的.</li>
<li>ETag处理.</li>
</ul>
<p>这个不细分析了, 有兴趣的童鞋可以自行围观代码(django/middleware/common.py), 我们接下来看SessionMiddleware. 这个中间件的process_request方法里面只有几行代码:</p>
<pre class="brush: python;">
class SessionMiddleware(object):
    def process_request(self, request):
        engine = import_module(settings.SESSION_ENGINE)
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
        request.session = engine.SessionStore(session_key)
</pre>
<p>这一段详细的理解可以参考<a href="http://docs.djangoproject.com/en/dev/topics/http/sessions/#topics-http-sessions">Django文档中有关会话的部分</a>. 这部分在我手头的项目中应用不多, 于是很不厚道地也不准备细看了.</p>
<p>AuthenticationMiddleware中的代码依然简洁:</p>
<pre class="brush: python;">
# django/contrib.auth.middleware.py
class AuthenticationMiddleware(object):
    def process_request(self, request):
        assert hasattr(request, 'session'), &quot;The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'.&quot;
        request.__class__.user = LazyUser()
        return None
</pre>
<p>除掉判断会话中间件是否已被载入的那段, 关键的语句是第5行, 这句设置了request这个WSGIRequest实例的类属性user, 将其设置为LazyUser, 而这个东西的定义为:</p>
<pre class="brush: python;">
class LazyUser(object):
    def __get__(self, request, obj_type=None):
        if not hasattr(request, '_cached_user'):
            from django.contrib.auth import get_user
            request._cached_user = get_user(request)
        return request._cached_user
</pre>
<p>这段代码使用了描述器(descriptor), 和上面那段代码的作用和起来所实现的功能是, 如果用request.user来请求user这个对象, 实际上请求的是_cached_user这个对象. 如果这个对象为空, 那么用get_user方法来获得当前请求的user:</p>
<pre class="brush: python;">
# django/contrib/auth/__init__.py
def get_user(request):
    from django.contrib.auth.models import AnonymousUser
    try:
        user_id = request.session[SESSION_KEY]
        backend_path = request.session[BACKEND_SESSION_KEY]
        backend = load_backend(backend_path)
        user = backend.get_user(user_id) or AnonymousUser()
    except KeyError:
        user = AnonymousUser()
    return user
</pre>
<p>这个函数的逻辑是, 从会话信息里面查找到当前用户信息, 查不到的话就将当前用户设置为匿名用户(未注册用户). 至此, 这个纠结的AuthenticationMiddleware处理request的部分就告一段落了.</p>
<h3>process_response</h3>
<p>CommonMiddleware中的这个方法是在处理ETag; SessionMiddleware的这个方法只是用来处理会话信息的保存, 谢天谢地. 而AuthenticationMiddleware更是很厚道地没有定义这个方法. 好吧, 那就这样结束吧~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/27/django-middleware/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django处理HTTP请求的过程</title>
		<link>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/</link>
		<comments>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/#comments</comments>
		<pubDate>Fri, 25 Dec 2009 08:07:28 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[REQUEST]]></category>
		<category><![CDATA[WSGI]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=204</guid>
		<description><![CDATA[WSGIHandler

HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:



<span class="readmore"><a href="http://blog.xiaket.org/2009/12/25/django-httpquest-procession/" title="Django处理HTTP请求的过程">阅读全文——共4456字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>WSGIHandler</h3>
<p>HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:</p>
<pre class="brush: python;">
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
</pre>
<p>这个句柄将处理HTTP请求, 我们跟进去看这个WSGI句柄的初始化. WSGIHandler这个类没有__init__方法, 因此使用的是base.BaseHandler的初始化方法:</p>
<pre class="brush: python;">
# django/core/handlers/base.py
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    response_fixes = [
        http.fix_location_header,
        http.conditional_content_removal,
        http.fix_IE_for_attach,
        http.fix_IE_for_vary,
    ]

    def __init__(self):
        self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
</pre>
<p>好吧, 这个里面貌似也没干啥&#8230; 我们继续看WSGIHandler的__call__方法. 这个方法会被调用上面的wsgi脚本中的语句调用.</p>
<p>根据WSGI的文档:</p>
<pre>
The Web framework/app is represented to the server as a Python callable. It can be
a class, an object, or a function. The arguments to __init__, __call__, or the function
must be as above: an environ object and a start_response callable.
</pre>
<p>参考看下WSGIHandler的__call__方法, 很符合这个要求, 也是两个外部参数.</p>
<pre class="brush: python;">
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        &quot;&quot;&quot; some code omitted here. &quot;&quot;&quot;
        signals.request_started.send(sender=self.__class__)
        try:
            try:
                request = self.request_class(environ)
            except UnicodeDecodeError:
                response = http.HttpResponseBadRequest()
            else:
                response = self.get_response(request)

            &quot;&quot;&quot; some more code omitted. &quot;&quot;&quot;
        finally:
            signals.request_finished.send(sender=self.__class__)

        try:
            status_text = STATUS_CODE_TEXT[response.status_code]
        except KeyError:
            status_text = 'UNKNOWN STATUS CODE'
        status = '%s %s' % (response.status_code, status_text)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', str(c.output(header=''))))
        start_response(status, response_headers)
        return response
</pre>
<p>这个方法一开始还做了些和中间件相关的事情, 这儿就省略了. 我们从信号这一段开始, 在处理完中间件相关的逻辑后, 此处放出一个request_started的信号, 我们可以利用这个信号来处理一些对每个HTTP请求都要处理的逻辑. 接下来我们开始处理请求. 这儿用了一个try-except-else语句块. 首先我们尝试将environ作为参数来生成一个WSGIRequest类, 如果这一步出了问题, 我们返回400, 否则我们用get_response(request)来获得一个HttpRequest对象. 接下来Django放出了一个request_finished信号, 然后返回这个HTTP请求. 我们继续看处理请求的这部分.</p>
<h3>WSGIRequest</h3>
<p>前面的代码中, 我们已经在WSGIHandler中将其request_class属性设为一个WSGIRequest的实例, 并用一个装有环境变量的字典environ初始化了一个WSGIRequest. 我们接下来看Django是怎么处理这个WSGIRequest的.</p>
<p>首先看这个类的初始化. 相关代码如下:</p>
<pre class="brush: python;">
class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = base.get_script_name(environ)
        path_info = force_unicode(environ.get('PATH_INFO', u'/'))
        if not path_info or path_info == script_name:
            &quot;&quot;&quot; some comments omitted here. &quot;&quot;&quot;
            path_info = u'/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s%s' % (script_name, path_info)
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        self._post_parse_error = False
</pre>
<p>这段初始化基本上也是比较平淡的, 主要做了两件事, 一个是设置了request.META这个字典, 另外设置了request.method. 如果这一段成功了, 那么我们将用get_response来处理这个WSGIRequest.</p>
<h3>get_response</h3>
<p>这个函数的定义在django/core/handlers/base.py中, 是BaseHandler的一个方法, 代码如下:</p>
<pre class="brush: python;">
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    def get_response(self, request):
        &quot;Returns an HttpResponse object for the given HttpRequest&quot;
        &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
        # Get urlconf from request object, if available.  Otherwise use default.
        urlconf = getattr(request, &quot;urlconf&quot;, settings.ROOT_URLCONF)
        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
        try:
            callback, callback_args, callback_kwargs = resolver.resolve(
                    request.path_info)
            &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
            # Actually, this line was in a try-except thing...   by xiaket.
            response = callback(request, *callback_args, **callback_kwargs)
            &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
            return response
        except http.Http404, e:
            if settings.DEBUG:
                from django.views import debug
                return debug.technical_404_response(request, e)
            else:
                try:
                    callback, param_dict = resolver.resolve404()
                    return callback(request, **param_dict)
                except:
                    try:
                        return self.handle_uncaught_exception(request, resolver, sys.exc_info())
                    finally:
                        receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
        &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
</pre>
<p>原来的代码很长, 仔细分析, 除掉了一些我不太关心的异常处理后就变成了现在这个样子. 这个函数接受一个HttpRequest实例, 返回一个HttpRequest实例. 函数一开始就来匹配URL. 第7行这句很强大, 因为它提供了很多可能性. 例如, 你可以通过中间件来对于request中的IP做判断, 然后通过设置urlconf来决定使用什么样的URL匹配方式. 当然, 这个时候已经经过了中间件, request.user已经设置好了, 你也可以根据用户的不同来提供不同的urlconf. 从settings.ROOT_URLCONF中拿到了URL配置后, 我们尝试匹配URL, 然后在第11行拿第10行匹配得到的适当的view函数来处理这个URL. 到视图函数以后就没有太多好说了. 中间那层我们略过, 返回了response之后也没啥了, 到这儿来就交给WSGI了. Django处理HTTP请求的逻辑也就结束了.</p>
<p>挖个坑, 实际上Django有很多HTTP请求的细节是在中间件里面完成的, 这篇文章完全没有提及这一点, 我有时间再仔细分析下中间件的作用, 为强大的中间件们正名~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>讨论一个纠结的Django编码问题</title>
		<link>http://blog.xiaket.org/2009/12/22/django-encoding-problem/</link>
		<comments>http://blog.xiaket.org/2009/12/22/django-encoding-problem/#comments</comments>
		<pubDate>Tue, 22 Dec 2009 13:54:08 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[GBK]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[REQUEST]]></category>
		<category><![CDATA[WSGI]]></category>
		<category><![CDATA[编码]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=199</guid>
		<description><![CDATA[简介

本文简单记录了我在项目中遇到的一个Django编码问题. 通过Django源码分析, 我们找到了问题的根源, 并提出了解决问题的方法.

问题总结: 用WSGI方式使用Django, 请求的URL是GBK编码, 而且在视图函数中使用了request.REQUEST字典的话, 可能会有编码问题.

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/22/django-encoding-problem/" title="讨论一个纠结的Django编码问题">阅读全文——共4454字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>简介</h3>
<p>本文简单记录了我在项目中遇到的一个Django编码问题. 通过Django源码分析, 我们找到了问题的根源, 并提出了解决问题的方法.</p>
<p>问题总结: 用WSGI方式使用Django, 请求的URL是GBK编码, 而且在视图函数中使用了request.REQUEST字典的话, 可能会有编码问题.</p>
<h3>问题背景</h3>
<p>问题的背景是这样的, 在我接手的报警项目中, 报警服务器提交到服务器的报警信息内容是GBK编码的, 例如下面的URL是一个典型的报警请求:</p>
<pre>

http://192.168.24.133/msggw.php?from_ip=1.2.3.4&#038;project_name=blah&#038;msg=blah+427%28%D0%A1%C3%B7%C9%B3%29+1.2.3.4+has+new+debug+log%2Ccheck%21%21%21%21%0ATue+Dec+22+08%3A01%3A18+2009%2822172096%29%28539088508%29%09shutdownOS%280%29
</pre>
<p>为了处理GBK编码, 我们要在视图函数里面设置request的编码, 例如:</p>
<pre class="brush: python;">
def receiving_message(request):
    request.encoding = &quot;gbk&quot;
</pre>
<p>这样, 在这个语句之后, 我们理论上应该不会再遇到编码问题. 但是不幸的是, 我还是发现有编码问题, 问题似乎来自我在视图函数的后半部分使用了request.REQUEST这个字典.</p>
<h3>代码背后的故事</h3>
<p>今天, 我在确定这个问题来自Django处理request后, 仔细读了下相关的源码. 在Django产生一个request对象到在视图函数里面使用request.GET/request.POST的具体细节包括:</p>
<h4>由WSGI脚本生成了一个request对象.</h4>
<p>相关代码在django/core/handlers/wsgi.py文件中, 生成的request对象是WSGIRequest的一个实例. 这个实例的初始化基本只是设置了request.META这个字典的内容. 接下来这个对象就被传给视图函数了.(不清楚中间具体还发生了什么事情, 哪天有机会再去仔细研究下&#8230;)</p>
<h4>在视图函数里面使用GET/POST字典</h4>
<p>这个过程看起来很简单, 实际上却比较复杂. 首先, 在WSGIRequest这个对象定义的结尾处, Django设置了使用GET/POST/REQUEST这三个字典的方法. 我们使用GET/POST字典实际上是通过执行这些内置的方法来实现的. 例如, 我们需要知道request.GET['msg']的内容, 此时, Django是通过_get_get方法来拿到GET字典的内容的:</p>
<pre class="brush: python;">
    def _get_get(self):
        if not hasattr(self, '_get'):
            # The WSGI spec says 'QUERY_STRING' may be absent.
            self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self._encoding)
        return self._get
</pre>
<p>Django文档中说, Django在处理GET/POST时是很懒惰的, 如果没有明确要GET/POST中的内容, Django是不会浪费时间生成这两个字典的. 上面这个函数里面的if就是一个明确的证据. 如果没有_get属性, 则实例化一个QueryDict(代码在django/http/__init__.py中), 并将其作为_get的内容, 也即是GET的内容. 在实例化的过程中给出了两个参数, 一个是QUERY_STRING, 一个是_encoding. 如果你还没有在视图函数里面设置编码, 那么此时_encoding是默认的值(utf-8). 在QueryDict的__init__方法里面, 我们看到这样的语句:</p>
<pre class="brush: python;">
        self.encoding = encoding
        for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
            self.appendlist(force_unicode(key, encoding, errors='replace'),
                            force_unicode(value, encoding, errors='replace'))
</pre>
<p>这一段代码负责生成GET字典的内容. 其中那个parse_qsl函数来自python默认的cgi模块, 作用是将QUERY_STRING分解为一个字典. 对于字典里面的每个值, Django使用force_unicode方法(代码在django/utils/encoding.py文件中)将其编码解码. 顺便说一句, 这个force_unicode函数会做一些诡异的事情, 例如将一个GBK字符串转码不成功且不论, 它会生成类似u&#8217;\u68a6\u5e7b:\u0421\xf7\u0273&#8242;的字符串, 很乱很杂交.</p>
<p>上面说了这么多, 总结成一句话: Django处理视图函数时, 直到你明确要求GET字典中的键值时, 这个字典才会被生成, 生成字典时按照编码的配置做了编码转码等工作.</p>
<h4>设置编码</h4>
<p>我们在文章的一开头就给出了设置Django处理QUERY_STRING的编码的方法, 即直接指定request的encoding. 这句话看起来简单, 实际上却在背后做了一些额外的工作, 这是因为在Django对HttpRequest对象(代码在django/http/__init__.py中)的定义中有这么一句:</p>
<pre class="brush: python;">
encoding = property(_get_encoding, _set_encoding)
</pre>
<p>即, 要取得编码的值, 实际上是调用了_get_encoding方法, 要设置编码的值, 实际上调用了_set_encoding方法. 我们关注的是后一种方法, 它的定义为:</p>
<pre class="brush: python;">
    def _set_encoding(self, val):
        &quot;&quot;&quot;
        Sets the encoding used for GET/POST accesses. If the GET or POST
        dictionary has already been created, it is removed and recreated on the
        next access (so that it is decoded correctly).
        &quot;&quot;&quot;
        self._encoding = val
        if hasattr(self, '_get'):
            del self._get
        if hasattr(self, '_post'):
            del self._post
</pre>
<p>很聪明很合乎逻辑的做法, 如果用户设置了编码, 那么我们把_get和_post都删除掉, 这样也就对应地删除了GET/POST这两个字典. 下次用户再想要拿这两个字典里面的内容时, 再用新的编码再生成一次GET/POST就是了.</p>
<h4>request.REQUEST字典</h4>
<p>这个字典是模仿php中的对应快捷方式而建立的, 实际上只是将GET和POST合并起来, 代码在django/core/handlers/wsgi.py的WSGIRequest定义中:</p>
<pre class="brush: python;">
    def _get_request(self):
        if not hasattr(self, '_request'):
            self._request = datastructures.MergeDict(self.POST, self.GET)
        return self._request
</pre>
<h3>哪段代码有问题?</h3>
<p>上面洋洋洒洒一大篇描述了Django处理GET/POST字典的原理, 我接下来要说的是这种处理过程中有哪些问题. 我们看看下面这段视图函数中的代码:</p>
<pre class="brush: python;">
def receiving_message(request):
    msg1 = request.REQUEST['msg']
    request.encoding = &quot;gbk&quot;
    msg2 = request.REQUEST['msg']
</pre>
<p>首先, 第一次拿到的msg1显然是乱码, 我们还没设置处理URL的编码, 因此URL没有被正确地解码. 但是不幸地是, 第二次拿到的msg2仍然是乱码. 这是因为设置编码的过程中仅仅删除了可能存在的GET和POST字典, 而没有删除REQUEST字典. 因此第二次我们从REQUEST字典里取东西时, 所拿到的结果仍然是按照不正确的编码解出来的结果.</p>
<p>这个问题出在哪儿? 虽然看起来应该认为上面的_set_encoding方法是有问题的, 应该额外加两行, 删除self._request才对. 不过问题不是那么简单的, HttpRequest对象里面根本没有request.REQUEST这个东东. 这个东东是在WSGIRequest中定义的. 我们应该在WSGIRequest的定义中override掉HttpRequest的默认_set_encoding方法才对.</p>
<p>再顺口提一句, modpython里面也没有override这个方法, 因此理论上也存在同样的问题.</p>
<h3>问题回顾</h3>
<p>上面说了很多, 但是为什么只有在很偶然的情况下才会出现编码问题呢? 在大多数情况下, request.REQUEST字典都没有生成. 因此在视图函数中, 设置编码后从request.REQUEST字典中拿东西出来是没有问题的. 但是在一定几率下(我只能这么说了&#8230;), 后面的视图函数代码中从request.REQUEST字典里拿到的数据可能是有编码问题的.</p>
<p>我在前面曾经推测这个问题的出现可能和request.REQUEST这个字典相关, 不过我在测试中发现, 即使这个字典没有出现, 也可能会出现编码问题. 哎哎, 无论如何, 这个是一个诡异的问题&#8230;</p>
<p>关于这个一定几率, 根据我的经验, 不是指某一段时间可以, 某一段时间不行; 而是某一次重启后可以, 某一次重启后不行&#8230; 也许这个问题在更深层次上和会话相关, 不过暂时还是不要这么钻牛角尖的好, 因为我们有一个比较放心的解决方案．</p>
<h3>解决方案</h3>
<p>比较放心也比较累赘的解决方案是在设置编码后, 将GET/POST/REQUEST都删掉:</p>
<pre class="brush: python;">
def receiving_message(request):
    request.encoding = &quot;gbk&quot;
    if hasattr(request, '_request'):
        del request._request
    if hasattr(request, '_get'):
        del request._get
    if hasattr(request, '_post'):
        del request._post
</pre>
<p>这样我想, 应该是不会再有问题了.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/22/django-encoding-problem/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>最近个人项目的设想</title>
		<link>http://blog.xiaket.org/2009/12/13/recent-personal-project-plan/</link>
		<comments>http://blog.xiaket.org/2009/12/13/recent-personal-project-plan/#comments</comments>
		<pubDate>Sat, 12 Dec 2009 16:39:57 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=171</guid>
		<description><![CDATA[自己手头积攒了很多电子书, 有些书虽然很少读, 但留下收藏毕竟还是不错的. 这些书虽然下载很方便, 但是整理却很是麻烦. 最大的问题是经常一本书会涉及到多个话题, 电子书分类时不知如何是好, 找起来也是要靠估摸. 于是想自己写一个有Web界面的个人图书管理系统. 好吧, 这个Web系统只有一个人用肯定是浪费了, 靠谱一点的想法是干脆做成一个图书交流系统. 这两天的空闲时间, 除了在写Django的中文localflavor, 就是在写这个. 有点点想法也就顺便记下来, 放在这儿. 用什么写? 嘿嘿, 自然是Django啦~

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/13/recent-personal-project-plan/" title="最近个人项目的设想">阅读全文——共1113字</a></span>]]></description>
			<content:encoded><![CDATA[<p>自己手头积攒了很多电子书, 有些书虽然很少读, 但留下收藏毕竟还是不错的. 这些书虽然下载很方便, 但是整理却很是麻烦. 最大的问题是经常一本书会涉及到多个话题, 电子书分类时不知如何是好, 找起来也是要靠估摸. 于是想自己写一个有Web界面的个人图书管理系统. 好吧, 这个Web系统只有一个人用肯定是浪费了, 靠谱一点的想法是干脆做成一个图书交流系统. 这两天的空闲时间, 除了在写<a href="http://code.google.com/p/django-localflavor-zhcn/">Django的中文localflavor</a>, 就是在写这个. 有点点想法也就顺便记下来, 放在这儿. 用什么写? 嘿嘿, 自然是Django啦~</p>
<h3>用户注册和权限分配</h3>
<p>用户注册这个步骤对于文件共享网站来说是必要的, 很正统的做法是生成一个随机字符串作为密钥, 往用户的邮箱发一封包含密钥的注册邮件. 用户通过点击链接而使用这个密钥注册. 这种做法对于小范围的文件共享网站却不是很合适. 填一个注册表单未免太生分太正式了, 毕竟大家都是同事, 低头不见抬头见, 弄这些繁文缛节倒彷佛是在捉弄人了. 另一个办法就是不限制用户对网站的访问. 如果仅仅开发下载权限也就算了, 最多多转转硬盘罢了. 但是无限制开发上传权限就不太好了&#8230; 于是我的想法是利用IP来判断用户. 利用Django放出的request_started信号来确定用户身份, 完成登录的工作. 基本逻辑是从信号里面拿到request对象. 这个对象里面的用户如果已登录, 则继续, 如果未登录, 则从request里面拿到用户的IP, 然后根据IP地址判断是否已知用户, 如果是已知用户, 则用login函数让其登录. 否则认为这个用户是受限用户, 不对其显示某些页面. 受限用户如何变成已知用户? 让他在泡泡上发个消息给我, 告诉我他的IP地址就行了~</p>
<h3>智能化地识别图书信息</h3>
<p>我们所上传的图书文件名里面一般包含了图书相关的信息. 例如书名, 出版社等等. 如果是pdf文件和chm文件, 我们还可以通过相应的python库来拿到电子书的作者/出版社等等信息. 为此, 我希望我能写出一个完美的, 省心地, 傻瓜地书籍上传页面, 你先上传一个文件, 这个文件名里面也许包含了一些信息, 我可以尝试使用re把这些信息抓出来. 然后, 我还可以从文件里面或者网络搜索而拿到书籍信息, 通过ajax显示在页面上. 这样书籍的信息也会更全面, 用户也更省心.</p>
<h3>批量上传</h3>
<p>写一个接口, 支持批量上传, 否则一本一本上传会很累的&#8230;</p>
<h3>用jQuery-UI写界面</h3>
<p>这个项目里面可以肆无忌惮地用比较大的js库了, 于是基本不用自己操心一堆乱七八糟的效果了, jQuery-UI这个库还是不错的, 写了几行代码就生成了一个很pp的界面~ 赞!</p>
<p>原本打算用ExtJS, 不过这个库据说版权限制很有问题, 还是用更流行而我也更熟悉的jQuery的UI效果库吧~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/13/recent-personal-project-plan/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>一个很有用的Django装饰器: render_to</title>
		<link>http://blog.xiaket.org/2009/12/04/django-decorator-render_to/</link>
		<comments>http://blog.xiaket.org/2009/12/04/django-decorator-render_to/#comments</comments>
		<pubDate>Fri, 04 Dec 2009 09:52:14 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[decorator]]></category>
		<category><![CDATA[Python开发]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=156</guid>
		<description><![CDATA[今天在pypi里面乱逛时看到一个装饰器, 觉得比较赞:



def render_to(template=None):

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/04/django-decorator-render_to/" title="一个很有用的Django装饰器: render_to">阅读全文——共1487字</a></span>]]></description>
			<content:encoded><![CDATA[<p>今天在pypi里面乱逛时看到一个装饰器, 觉得比较赞:</p>
<pre class="brush: python;">
def render_to(template=None):
    &quot;&quot;&quot;
    Decorator for Django views that sends returned dict to render_to_response function.

    Template name can be decorator parameter or TEMPLATE item in returned dictionary.
    RequestContext always added as context instance.
    If view doesn't return dict then decorator simply returns output.

    Parameters:
     - template: template name to use

    Examples:
    # 1. Template name in decorator parameters

    @render_to('template.html')
    def foo(request):
        bar = Bar.object.all()
        return {'bar': bar}

    # equals to
    def foo(request):
        bar = Bar.object.all()
        return render_to_response('template.html',
                                  {'bar': bar},
                                  context_instance=RequestContext(request))
    # 2. Template name as TEMPLATE item value in return dictionary

    @render_to()
    def foo(request, category):
        template_name = '%s.html' % category
        return {'bar': bar, 'TEMPLATE': template_name}

    #equals to
    def foo(request, category):
        template_name = '%s.html' % category
        return render_to_response(template_name,
                                  {'bar': bar},
                                  context_instance=RequestContext(request))

    &quot;&quot;&quot;
    def renderer(function):
        def wrapper(request, *args, **kwargs):
            output = function(request, *args, **kwargs)
            if not isinstance(output, dict):
                return output
            tmpl = output.pop('TEMPLATE', template)
            return render_to_response(tmpl, output, context_instance=RequestContext(request))
        return wrapper
    return renderer
</pre>
<p>这个装饰器出自<a href="http://pypi.python.org/pypi/django-annoying/0.7.4">django-annoying</a>. 在annoying目录下的decorators.py文件中.</p>
<p>优点: 写视图函数的时候, 总是写一个很长的render_to_response函数, 这个里面的东西都是很固定的. 于是每次都是y来p去, 很不爽. 如果用这个装饰器就可以很一目了然地在视图函数中用docstring里面的格式来写视图函数了. 这个时候, 视图函数返回一个字典就可以了.</p>
<p>缺点: 对于熟悉Django的用户, 这个可读性稍差了一点点. 不过我想习惯了这个装饰器的人都会更爱这个吧, 毕竟它更pythonic, 更DRY.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/04/django-decorator-render_to/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Django自定义admin界面 – Part3</title>
		<link>http://blog.xiaket.org/2009/12/01/django-admin-customize-part3/</link>
		<comments>http://blog.xiaket.org/2009/12/01/django-admin-customize-part3/#comments</comments>
		<pubDate>Tue, 01 Dec 2009 15:59:07 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[django-admin]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=117</guid>
		<description><![CDATA[前面的文章里面介绍了自定义Django的admin的几种技巧. 今天在这儿补充一点最新的经验. 今天要说的内容实际上和admin没有特别直接的联系, 但是问题的产生和解决都是在admin里面完成的.

背景介绍

在我手头的项目里面需要利用Django的信号机制来完成一些额外的工作. 可惜的是, 直到现在, Django还没有一个官方的解决方案来为ManyToManyField提供信号机制. 不过, 这个问题早已有人注意到了, 在Django的Trac上有一个ticket就是专门解决这个问题的, 这个ticket的状态目前是assigned, 而且已经有一个能用的patch.

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/01/django-admin-customize-part3/" title="Django自定义admin界面 – Part3">阅读全文——共2912字</a></span>]]></description>
			<content:encoded><![CDATA[<p>前面的文章里面介绍了自定义Django的admin的几种技巧. 今天在这儿补充一点最新的经验. 今天要说的内容实际上和admin没有特别直接的联系, 但是问题的产生和解决都是在admin里面完成的.</p>
<h3>背景介绍</h3>
<p>在我手头的项目里面需要利用Django的信号机制来完成一些额外的工作. 可惜的是, 直到现在, Django还没有一个官方的解决方案来为ManyToManyField提供信号机制. 不过, 这个问题早已有人注意到了, 在Django的Trac上有<a href="http://code.djangoproject.com/ticket/5390">一个ticket</a>就是专门解决这个问题的, 这个ticket的状态目前是assigned, 而且已经有一个能用的patch.</p>
<p>但是, 这个patch在admin界面下并不能正确给出相应的信号. 例如, 你减少了一个ManyToManyField里面的一项, 逻辑上你期待Django在处理时应该将这一项从数据表里面删除, 然后释放一个remove信号. 但事实上, Django会首先清空数据表里面的所有项, 然后将剩下的项一古脑地添加回来.</p>
<h3>顺藤摸瓜</h3>
<p>首先应该肯定的是, 这个patch应该是没有大问题的, 这个patch本身提供了一些doctest, 从doctest来看, 该释放的信号都正确释放了. 没有出现上面无厘头的清空后再添加的情况.</p>
<p>接下来, 我们要做一点顺藤摸瓜的工作. 我已经知道, 在admin界面中作出修改后, 逻辑部分的视图函数是django/contrib/admin/options.py文件里面的change_view. 这个函数中和保存ManyToMany关系相关的代码是:</p>
<pre class="brush: python;">
            if all_valid(formsets) and form_validated:
                self.save_model(request, new_object, form, change=True)
                form.save_m2m()
                for formset in formsets:
                    self.save_formset(request, form, formset, change=True)

                change_message = self.construct_change_message(request, form, formsets)
                self.log_change(request, new_object, change_message)
                return self.response_change(request, new_object)
</pre>
</p>
<p>显而易见, 保存m2m关系的实际上是form这个ModelForm实例的save_m2m方法. 这个方法相关的代码在django/forms/models.py文件中:</p>
<pre class="brush: python;">
    def save_m2m():
        opts = instance._meta
        cleaned_data = form.cleaned_data
        for f in opts.many_to_many:
            if fields and f.name not in fields:
                continue
            if f.name in cleaned_data:
                f.save_form_data(instance, cleaned_data[f.name])
</pre>
<p>仔细观察这个函数可以发现, 直到执行最后一句f.save_form_data之前, 都还没有做实质性的操作. 因此, 前面所叙述的逻辑错误肯定是在save_form_data这个函数中出错了. 这个函数的代码在django/db/models/fields/related.py中, 一共就一行:</p>
<pre class="brush: python;">
    def save_form_data(self, instance, data):
        setattr(instance, self.attname, data)
</pre>
<p>当时看到这个就傻眼了, 因为这个setattr是python内置的函数, 我去研究了一圈python的C源码, 没有看懂任何东西. 百无聊赖地翻着django/db/models/fields/related.py这个文件, 看到下面这段:</p>
<pre class="brush: python;">
class ManyRelatedObjectsDescriptor(object):
    def __set__(self, instance, value):
        &quot;&quot;&quot;Some code here&quot;&quot;&quot;

        manager = self.__get__(instance)
        manager.clear()
        manager.add(*value)
</pre>
<p>于是就知道问题出在哪儿了&#8230;</p>
<h3>别人的解决方案</h3>
<p>好吧, 就我这种水平也很难写出什么更深入的讨论了. 找到上面这个错误后, 我兴冲冲地去ticket页面发表意见. 发表完以后才看到另一个人已经给出了<a href="http://code.djangoproject.com/ticket/5390#comment:40">一个直接的解决方案</a>: </p>
<pre class="brush: python;">
        # Old code
        manager = self.__get__(instance)
        manager.clear()
        manager.add(*value)

        # New code
        manager = self.__get__(instance)
        previous=set(manager.all())
        new=set(value)
        added=new-previous
        removed=previous-new
        manager.add(*added)
        manager.remove(*removed)
</pre>
<p>这个解决方案很直观, 在用集合的减法那段写得很漂亮. 但是存在几个问题:</p>
<ul>
<li>没有正确释放出clear信号</li>
<li>没有对added和removed这两个作一个判定就直接丢给了add和remove. 更合理的方法应是检查发现它们非空后再交给后面的函数处理.</li>
<li>没有经过完善地测试, 没有被收入官方的patch. 我们的服务器上已经部署好了旧patch, 我不希望再打一个没有验证过的补丁.</li>
</ul>
<h3>我的解决方案</h3>
<p>文章一开头我就说了, 这个问题我完全是在admin的范围内解决的. 没有hack掉Django的源码(虽然很明显它是有问题的). 我的思路也很简单: 将整段代码全部放在admin.py里面, override掉默认的方法. 这一段就比较丑陋了, 将数个函数叠在一起可不好看. 于是这部分代码我就略过了. 给出核心部分的代码吧:</p>
<pre class="brush: python;">
        manager = self.__get__(instance)
        previous=set(manager.all())
        new=set(value)
        if previous and not new:
            return manager.clear()
        added=new-previous
        removed=previous-new
        if removed:
            manager.remove(*removed)
        if added:
            manager.add(*added)
</pre>
<p>相对于原有的解决方案, 我的代码主要有三个改变: </p>
<ul>
<li>添加了对new为空时的判断来调用clear方法.</li>
<li>添加了对add和remove的判断.</li>
<li>改变了add和remove的顺序, 希望这样能够更快些.</li>
</ul>
<p>十二点了, 懒得进一步总结了, 反正该说的也差不多说完了, 就酱紫吧~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/01/django-admin-customize-part3/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Pro Django阅读笔记: 第二章 第一部分</title>
		<link>http://blog.xiaket.org/2009/11/21/pro-django-ch2-notes-1/</link>
		<comments>http://blog.xiaket.org/2009/11/21/pro-django-ch2-notes-1/#comments</comments>
		<pubDate>Sat, 21 Nov 2009 11:43:33 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[读书笔记]]></category>
		<category><![CDATA[django-orm]]></category>
		<category><![CDATA[metaprogramming]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=72</guid>
		<description><![CDATA[Pro Django是由Marty Alchin写的一本关于Django高级技巧的技术书籍. 由Apress出版, 我手头的是09年的第一版. 书籍相关信息及链接如下:



Appress上的书籍主页

<span class="readmore"><a href="http://blog.xiaket.org/2009/11/21/pro-django-ch2-notes-1/" title="Pro Django阅读笔记: 第二章 第一部分">阅读全文——共3784字</a></span>]]></description>
			<content:encoded><![CDATA[<p>Pro Django是由Marty Alchin写的一本关于Django高级技巧的技术书籍. 由Apress出版, 我手头的是09年的第一版. 书籍相关信息及链接如下:</p>
<ul>
<li><a href="http://www.apress.com/book/view/1430210478">Appress上的书籍主页</a></li>
<li><a href="http://prodjango.com/">疑似作者架设的书籍主页</a></li>
<li><a href="http://martyalchin.com/">作者博客</a></li>
</ul>
<p>这本书不适合Django初学者学习, 但是对于已经入门的又需要进一步学习的开发者却很有针对性. 我准备在这儿写一下这本书的读书笔记, 也逼着自己能够继续看完这本书&#8230;第一章基本是闲扯, 虽然概念上很重要, 但是没有提供任何直接的技巧, 就略过了&#8230;这篇文章主要是关于第二章: Django中的python技巧.</p>
<h3>Python中类的创建</h3>
<h4>动态类创建:使用type函数</h4>
<p>要创建一个类, 比较大众化的方法是酱紫的:</p>
<pre class="brush: python;">
&gt;&gt;&gt; class NormalClass(object):
...     print 'Loading NormalClass...',
...     spam = 'eggs'
...     print &quot;done&quot;
...
Loading NormalClass... done
&gt;&gt;&gt; NormalClass
&lt;class '__main__.NormalClass'&gt;
&gt;&gt;&gt; NormalClass.spam
'eggs'
</pre>
<p>除了这种方法之外, 还可以用另外一种方式来创建类:</p>
<pre class="brush: python;">
&gt;&gt;&gt; DynamicClass = type('DynamicClass', (object,), {'spam': 'eggs'})
&gt;&gt;&gt; DynamicClass
&lt;class '__main__.DynamicClass'&gt;
&gt;&gt;&gt; DynamicClass.spam
'eggs'
</pre>
<p>上面这种方面里面用到了type函数. 一般来说, 我们是将一个对象作为参数传递给type函数, 这种情况下type函数的作用是给出这个对象的类型. 而当type接受三个参数时, type函数可视作是class语句的动态形式. 此时, 三个参数依次为新类名, 基类和包含新类属性的字典. 用type来动态构建类是很方便的.</p>
<h4>用元类(metaclass)来改变类的属性</h4>
<p>type实际上是一个元类, 元类是能够创建其它类的类. 元类编程所要实现的目的是在代码运行时而不是在程序编写时创建和改变代码. 例如上面的类定义的例子, 类的名字是代码运行时的一个变量. Python对元类编程的支持是通过允许在类定义里面包括一个元类而实现的. 如果一个类的定义里面包含了对__metaclass__属性的定义, 那么我们不会用默认的type方法来创建这个类, 而是用这个__metaclass__来创建我们定义的类:</p>
<pre class="brush: python;">
&gt;&gt;&gt; class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print 'Defining %s' % cls
...         print 'Name: %s' % name
...         print 'Bases: %s' % (bases, )
...         print 'Attributes:'
...         for (name, value) in attrs.items():
...             print '    %s: %r' % (name, value)
...
&gt;&gt;&gt; class RealClass(object):
...     __metaclass__ = MetaClass
...     spam = 'eggs'
...
Defining &lt;class '__main__.RealClass'&gt;
Name: RealClass
Bases: (&lt;type 'object'&gt;,)
Attributes:
    __module__: '__main__'
    __metaclass__: &lt;class '__main__.MetaClass'&gt;
    spam: 'eggs'
&gt;&gt;&gt; RealClass
&lt;class '__main__.RealClass'&gt;
</pre>
<p>注意哦, 这个类没有实例化, 我们在创建这个类的过程中就执行了MetaClass中的代码. 在这个例子中, MetaClass定义了一个__init__方法, 而在Django内部代码里面, 更多的元类编程是在__new__方法中完成的. 另外, 关于python的元类编程(metaprogramming), 下面有三篇文档, 包括一篇wikipedia上的介绍和IBM DevWorks上两篇不错的文章:</p>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Metaprogramming">Metaprogramming简介</a></li>
<li><a href="http://www.ibm.com/developerworks/cn/linux/l-pymeta/index.html">Python 中的元类编程</a></li>
<li><a href="http://www.ibm.com/developerworks/cn/linux/l-pymeta2/index.html">Python 中的元类编程，第 2 部分</a></li>
</ul>
<h4>实际的元类编程例子: Django中的模型定义</h4>
<p>下面是一段典型的Django创建模型的代码:</p>
<pre class="brush: python;">
class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = [&quot;horn_length&quot;]
        verbose_name_plural = &quot;oxen&quot;
</pre>
<p>现在我们一起来体会下这段代码执行时到底干了什么. 首先需要说明的是, Ox这个类的基类是models.Model, 相关代码在django/db/models/base.py. 而Model这个类是由ModelBase这个元类生成的:</p>
<pre class="brush: python;">
class ModelBase(type):
    &quot;&quot;&quot;
    Metaclass for all models.
    &quot;&quot;&quot;
    def __new__(cls, name, bases, attrs):
+---170 lines: super_new = super(ModelBase, cls).__new__------------------

class Model(object):
    __metaclass__ = ModelBase
    &quot;&quot;&quot;some more code&quot;&quot;&quot;
</pre>
<p>这种布局, 或者说代码结构的好处, 在于在Ox模型的定义过程中隐藏了元类声明, 因为这理应是比较低层的内容, 不应该让普通用户过多干涉. 另一个比较显著的好处在于Ox模型能够方便的继承models.Model里定义的各种方法. 当然, 这样布局后, Ox模型的定义也很清晰易懂, 更pythonic. 读完上面的代码, 会发现类的定义里面还有一个子类, 叫Meta. 简单看了下代码, 虽然名字一样, 但是这段似乎和元类编程没有直接的关系. 尽管Meta里面定义的内容是在ModelBase的__new__方法里面处理而作为_meta来保存的.</p>
<p>关于Django的ORM的进一步讨论会在第三章里面进行~</p>
<h3>各种数据类型的通用方法</h3>
<p>注意下, 这些是约定俗称的通用方法, 而不是确定一定以及肯定有的方法. 对于Django来说, 这些类型都是具有这些方法的, 对于较大的Django的库来说也是这样. 但是不要太想当然&#8230;</p>
<h4>可调用的类型</h4>
<p>此处, 可调用的类型包括函数, 类和类方法都是可调用的. 另外, 如果一个类定义了__call__方法, 那么这个类的实例也是可调用的:</p>
<pre class="brush: python;">
&gt;&gt;&gt; class Multiplier(object):
...     def __init__(self, factor):
...         self.factor = factor
...     def __call__(self, value):
...         return value * self.factor
...
&gt;&gt;&gt; times2 = Multiplier(2)
&gt;&gt;&gt; times2(5)
10
&gt;&gt;&gt; times3 = Multiplier(3)
&gt;&gt;&gt; times3(10)
30
</pre>
<p>在这一段代码中, 我们首先定义了一个乘法器的类, 这个类初始化时接受一个参数, 乘数因子. 这个类的实例能够被调用, 接受一个参数, 被乘数. 例如, 在第7行, 括号中的2是类初始化参数. 初始化后times2是一个Multiplier类的一个实例. 之后我们调用times2(5)是将5作为参数交给Multiplier类中的__call__方法. 另外提一句, Python还提供了一个callable函数来判断一个对象是不是可以被调用的.</p>
<h4>字典</h4>
<p>一般字典类型或者类似字典类型都会提供下面三种方法: __contains__(self, key), __getitem__(self, key), __setitem__(self, key, value). 其中__contains__方法一般通常是通过in运算符使用.</p>
<h4>可迭代的类型</h4>
<p>可迭代的类型是指能够送进for循环的类型. 列表, 元组, 字典等都是可迭代的. 如果你的类想要变成一个可迭代的类型, 那么你需要定义自己的__iter__方法:</p>
<pre class="brush: python;">
&gt;&gt;&gt; class Fibonacci(object):
...     def __init__(self, count):
...         self.count = count
...     def __iter__(self):
...         a, b = 0, 1
...         for x in range(self.count):
...             if x &lt; 2:
...                 yield x
...             else:
...                 c = a + b
...                 yield c
...                 a, b = b, c
...
&gt;&gt;&gt; for x in Fibonacci(5):
...     print x,
...
0 1 1 2 3
&gt;&gt;&gt; for x in Fibonacci(10):
...     print x,
...
0 1 1 2 3 5 8 13 21 34
</pre>
<p>今天就酱紫吧, 明天有时间继续.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/11/21/pro-django-ch2-notes-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django自定义admin界面 &#8211; Part2</title>
		<link>http://blog.xiaket.org/2009/11/18/django-admin-customize-part2/</link>
		<comments>http://blog.xiaket.org/2009/11/18/django-admin-customize-part2/#comments</comments>
		<pubDate>Wed, 18 Nov 2009 05:49:18 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[django-admin]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=47</guid>
		<description><![CDATA[接着昨天的内容写  

为不同的用户提供不同的域集(Fieldset)

这个不算是很复杂的hack, 在ModelAdmin里面定义一个域集, 然后用自定义的get_fieldsets方法override掉默认的方法即可:

<span class="readmore"><a href="http://blog.xiaket.org/2009/11/18/django-admin-customize-part2/" title="Django自定义admin界面 &#8211; Part2">阅读全文——共4219字</a></span>]]></description>
			<content:encoded><![CDATA[<p>接着昨天的内容写 <img src='http://blog.xiaket.org/wp-includes/images/smilies/icon_biggrin.gif' alt=':D' class='wp-smiley' /> </p>
<h3>为不同的用户提供不同的域集(Fieldset)</h3>
<p>这个不算是很复杂的hack, 在ModelAdmin里面定义一个域集, 然后用自定义的get_fieldsets方法override掉默认的方法即可:</p>
<pre class="brush: python;">
class ProjectAdmin(admin.ModelAdmin):
    selected_fieldsets = (
        (None, {'fields': ('fullname', 'shortname', 'popo_acount', \
            'sms_time', 'maintain_time', 'sa', 'programmer', 'rules')}),
    )

    def get_fieldsets(self, request, obj=None):
        &quot;&quot;&quot;
        This function would limit the fieldset for project admins.
        For project admins, we used selected_fieldsets.
        &quot;&quot;&quot;
        if request.user.is_superuser:
            # Show me everything, for I'm root.
            return super(ProjectAdmin, self).get_fieldsets(request, obj)
        elif request.user.get_profile().is_admin():
            # Show project admins selected fields.
            return self.selected_fieldsets
</pre>
<p>在上面的例子里面, 我们用request里面的用户信息来对用户加以区分, 如果是超级用户, 那么调用父类的默认方法, 不要override, 而当用户是项目管理员时, 直接返回选择过的, 没有关键信息的域集(Fieldset). 这样能够限制用户对关键信息的访问和修改.</p>
<h3>权限控制</h3>
<p>Django对于模型有具体的权限控制, 例如, 你可以规定这个用户有添加, 修改或删除User模型的权限. 但是当需要根据管理员身份而对这个权限加以细分时, 这个权限控制就不够了, 我们需要自定义has_add_permission, has_change_permission等方法. 在我的项目中, 我自定义了后者:</p>
<pre class="brush: python;">
    def has_change_permission(self, request, obj=None):
        &quot;&quot;&quot;
        Returns True if the given request has permission to change the given
        Django model instance.

        If `obj` is None, this should return True if the given request has
        permission to change *any* object of the given type.
        &quot;&quot;&quot;
        if obj:
            project = obj
            user = request.user
            if user.is_superuser:
                return True
            else:
                project_list = request.user.get_profile().project_set.all()
                if project in project_list:
                    return True
                else:
                    return False
</pre>
<p>这个依然不复杂, 核心思想仍然是根据request中的用户信息而对返回的内容加以控制.</p>
<h3>自定义保存方法</h3>
<p>save_model是一个很多人都会修改的方法, 自定义保存方法能够让你在保存时做一些额外的事情. 比如在IBM DevWork上那篇文章就用save_model方法来保存了request里面的用户信息. 好吧, DevWork上这篇文章并不是一个特别好的例子, 因为这种事情最好在视图函数里面搞定, 再不济也该写一个信号自动完成, 放在这儿是不恰当的. 而且这篇文章里面给的例子相对较简单, 我要做的事情会更复杂一点. 我需要自定义的是, 当admin界面下模型的修改提交后, 我需要根据用户权限来对m2m关系的变化加以限制. 因此, 我还需要修改form默认的save_m2m方法, 废话少说, 放代码先~</p>
<pre class="brush: python;">
    def save_model(self, request, instance, form, change):
        &quot;&quot;&quot;
        Like the save_model in MyUserAdmin, we need to setup the value of an
        m2m field in this function.
        &quot;&quot;&quot;
        def my_save_m2m():
            # We have two m2m fields in Project model, the
            # 'admins' field and the 'rules' field.
            for field in instance._meta.many_to_many:
                if field.name == 'admins':
                    # We just don't save it, meaning this field cannot be
                    # changed by this user.
                    pass
                else:
                    cleaned_data = form.cleaned_data
                    field.save_form_data(instance, cleaned_data[field.name])

        instance.save()
        user = request.user
        if user.get_profile().is_admin() and not user.is_superuser:
            form.save_m2m = my_save_m2m
</pre>
<p>诸位看官先表看那个my_save_m2m方法, 先看后面的. 首先老老实实地用instance.save()来保存这个模型里面的非m2m关系. 然后根据用户的类型来对用户执行的save_m2m方法加以修改. 逻辑判断不细说了, 只是当用户满足某个条件时, 则form的save_m2m方法就会使用我们上面定义的my_save_m2m方法.</p>
<p>再来回头看我定义的my_save_m2m方法, 因为我所要保存的这个模型有两m2m关系, 因此我需要根据修改的具体的域的名字来加以区分, 这个信息在instance._meta.many_to_many里面有, 具体判断的方法参见代码了. 注意到第13行用的pass表示不对这个域的m2m关系加以保存. 而后面第15, 16两行的代码表示按照默认的方法保存(实际上这个是抄默认的m2m关系的, 嘿嘿~).</p>
<h3>自定义MultiSelect中出现的值</h3>
<p>好吧, 这是一个很特殊的需求. 我需要根据不同的用户而在一个m2m的MultiSelect控件中显示不同的值. 对于超级用户, 我希望他/她能看到所有可能的值, 对于有项目管理员(具有部分权限的用户), 我希望他/她只能看到和自己项目相关的内容.</p>
<p>这一个需求在参考资料2中的那个Slide的第50页有一个类似的实现, 不过这个实现相对更简单, 因为它没有根据用户的不同而加以处理, 而仅仅是根据指定的值来修改控件中的值. 事实上, 如果不做进一步的修正, 在ModelForm中也不可能根据用户的不同而筛选出不同的值, 因为逻辑上一个ModelForm里面不应该有请求相关的信息, 于是也就不能做这种筛选了. 于是, 为了实现我的需求, 我强制的将request作为参数而在实例化这个ModelForm的时候传给了form, 为此, 我需要override默认的change_view方法:</p>
<pre class="brush: python;">
form = MyUserprofileForm
def change_view(self, request, object_id, extra_context=None):
    &quot;&quot;&quot; some code here. &quot;&quot;&quot;
    if request.method == 'POST':
        &quot;&quot;&quot; more code here. &quot;&quot;&quot;
    else:
        form = ModelForm(instance=obj, request = request)
        &quot;&quot;&quot; even more code here. &quot;&quot;&quot;
</pre>
<p>上面这几行代码只是给出了change_view这个很长的函数的梗概, 我所修改的是第7行. 将一个request作为参数传给了ModelForm. 然后, 我们需要定义自己的ModelForm:</p>
<pre class="brush: python;">
class MyUserprofileForm(forms.ModelForm):
    &quot;&quot;&quot;
    To show only those projects that can be managed by the project admin,
    we have to setup our form manually.
    &quot;&quot;&quot;
    def __init__(self, *args, **kwargs):
        request = kwargs.get('request', None)
        if request:
            del kwargs['request']
        super(MyUserprofileForm, self).__init__(*args, **kwargs)
        if request:
            userprofile = request.user.get_profile()
            # Set project widget
            project_widget = self.fields['projects'].widget
            if userprofile.user.is_superuser:
                projects = Project.objects.all()
            elif userprofile.is_admin():
                projects = userprofile.project_set.all()
            choices = []
            for choice in projects:
                choices.append((choice.id, choice.fullname))
            project_widget.choices = choices

    class Meta:
        model = Userprofile
</pre>
<p>在__init__函数的一开始, 我们就试图从kwargs中拿到request变量. 然后将request从kwargs中删除后直接调用原有的__init__方法来完成正常的初始化过程. 如果kwargs中有request, 我们就需要根据request中的用户信息来对project对应的控件加以修改. 这一段代码可读性比较好, 而且比较死, 因此就不再多解释了~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/11/18/django-admin-customize-part2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
