﻿<?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>礁湖星云</title>
	<atom:link href="http://www.icyfire.me/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.icyfire.me</link>
	<description>太阳底下没有新鲜事。</description>
	<lastBuildDate>Sun, 01 Apr 2012 04:58:58 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>为Nginx安装Passenger模块</title>
		<link>http://www.icyfire.me/2012/03/nginx-passenger-module/</link>
		<comments>http://www.icyfire.me/2012/03/nginx-passenger-module/#comments</comments>
		<pubDate>Sat, 03 Mar 2012 04:57:51 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[Passenger]]></category>
		<category><![CDATA[Rails]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1247</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2012/03/nginx_passenger_eyecatcher-640x257.png" alt="" title="nginx_passenger" width="640" height="257" class="alignnone size-medium wp-image-1258" /> <a href="http://www.icyfire.me/2012/03/nginx-passenger-module/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.icyfire.me/wp-content/uploads/2012/03/nginx_passenger_eyecatcher-640x257.png" alt="" title="nginx_passenger" width="640" height="257" class="alignnone size-medium wp-image-1258" /></p>
<p>之前安装了Redmine，但是无论使用本身内置的Web Server还是Mongrel都不是个很好的选择。因为本身服务器安装了Nginx，所以想在Nginx下跑Rails，于是选择了<a href="http://www.modrails.com/index.html" target="_blank">Passenger</a>。</p>
<p>首先下载passenger，并执行里面安装Nginx模块的命令：</p>
<pre class="brush: bash; gutter: false;">
wget http://rubyforge.org/frs/download.php/75548/passenger-3.0.11.tar.gz
tar zxvf passenger-3.0.11.tar.gz
cd passenger-3.0.11/bin/
./passenger-install-nginx-module
</pre>
<p>然后按照提示进行安装。</p>
<p>它会检查系统是否已经安装了必须的软件：</p>
<pre class="brush: bash; gutter: false;">
Checking for required software...

 * GNU C++ compiler... found at /usr/bin/g++
 * The &#039;make&#039; tool... found at /usr/bin/make
 * A download tool like &#039;wget&#039; or &#039;curl&#039;... found at /usr/bin/wget
 * Ruby development headers... found
 * OpenSSL support for Ruby... found
 * RubyGems... found
 * Rake... found at /usr/local/bin/rake
 * rack... found
 * Curl development headers with SSL support... found
 * OpenSSL development headers... found
 * Zlib development headers... found
</pre>
<p>没有安装的话，它会提示你安装的方法。</p>
<p>然后，它会让你选择是自动下载安装Nginx还是自定义安装：</p>
<pre class="brush: bash; gutter: false;">
Automatically download and install Nginx?

Nginx doesn&#039;t support loadable modules such as some other web servers do,
so in order to install Nginx with Passenger support, it must be recompiled.

Do you want this installer to download, compile and install Nginx for you?

 1. Yes: download, compile and install Nginx for me. (recommended)
    The easiest way to get started. A stock Nginx 1.0.10 with Passenger
    support, but with no other additional third party modules, will be
    installed for you to a directory of your choice.

 2. No: I want to customize my Nginx installation. (for advanced users)
    Choose this if you want to compile Nginx with more third party modules
    besides Passenger, or if you need to pass additional options to Nginx&#039;s
    &#039;configure&#039; script. This installer will  1) ask you for the location of
    the Nginx source code,  2) run the &#039;configure&#039; script according to your
    instructions, and  3) run &#039;make install&#039;.

Whichever you choose, if you already have an existing Nginx configuration file,
then it will be preserved.

Enter your choice (1 or 2) or press Ctrl-C to abort:
</pre>
<p>由于我本身已经安装有Nginx，所以选择2。然后它会让你选择源代码的路径，安装的目标路径以及编译参数。这里我的安装路径跟编译参数都跟以前安装Nginx时一致（可以运行nginx -V命令获取到以前的编译参数），这样就不会影响到以前安装的Nginx了。需要注意的事，安装Passenger模块并不会影响到已经存在的Nginx配置文件，但是为了安全起见，安装前还是先备份一下吧！</p>
<p>之后就是编译及安装了，成功的话你会看到这句话：Nginx with Passenger support was successfully installed.</p>
<p>现在我们可以编辑Nginx的配置文件让它支持Passenger，以及把一个Rails应用配置上去。</p>
<pre class="brush: text; gutter: false;">
http {
    ...
    passenger_root /path/to/passenger-3.0.11;
    passenger_ruby /usr/local/bin/ruby;

    server {
        listen 80;
        server_name www.yourhost.com;
        root /somewhere/public;
        passenger_enabled on;
    }
    ...
}
</pre>
<p>提示：<br />
1. 如果编译安装过程中遇到‘ngx_http_upstream_conf_t’ has no member named ‘module’这个错误，可能是你的Nginx版本不够高，请尝试使用一个较新的版本。</p>
<p>其他：<br />
1. 更多的安装信息请参考：<a href="http://www.modrails.com/install.html" target="_blank">http://www.modrails.com/install.html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2012/03/nginx-passenger-module/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux下Redmine的安装</title>
		<link>http://www.icyfire.me/2012/03/linux-redmine-install/</link>
		<comments>http://www.icyfire.me/2012/03/linux-redmine-install/#comments</comments>
		<pubDate>Fri, 02 Mar 2012 09:29:24 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[Redmine]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1245</guid>
		<description><![CDATA[<div><img src="http://www.icyfire.me/wp-content/uploads/2012/03/redmine_logo_v1.png" alt="" title="Redmine" width="600" height="200" class="alignnone size-full wp-image-1251" /></div> <a href="http://www.icyfire.me/2012/03/linux-redmine-install/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>1. 首先安装Ruby，由于最新的稳定版1.3不支持1.9.x，所以下载1.8.7版本。另外我采用源码的方式安装：</p>
<pre class="brush: bash; gutter: true;">
wget http://www.mirrorservice.org/sites/ftp.ruby-lang.org/pub/ruby/ruby-1.8.7-p358.tar.gz
tar zxvf ruby-1.8.7-p358.tar.gz
cd ruby-1.8.7-p358
./configure
make
make install
</pre>
<p>2. 安装<a href="http://rubygems.org/" target="_blank">RubyGem</a>，Redmine 1.3.x要求RubyGems <= 1.7，但是我用最新版暂时没发生什么问题。</p>
<pre class="brush: bash; gutter: true;">
wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.17.tgz
tar zxvf rubygems-1.8.17.tgz
cd rubygems-1.8.17
ruby setup.rb
</pre>
<p>3. 安装Rails。1.3.x版本的Redmine要求Rails的版本为2.3.14。</p>
<pre class="brush: bash; gutter: true;">
gem install rails -v=2.3.14
</pre>
<p>4. 安装必要插件，一个是用作国际化的i18n，一个是连接mysql数据库的ruby-mysql。</p>
<pre class="brush: bash; gutter: true;">
gem install i18n -v=0.4.2
gem install ruby-mysql
</pre>
<p>这里我使用mysql作为数据库，具体安装方法就不说了。这里假设mysql已经在服务器上安装过了。</p>
<p>5. 安装以及配置Redmine<br />
首先，下载Redmine并解压：</p>
<pre class="brush: bash; gutter: true;">
wget http://rubyforge.org/frs/download.php/75814/redmine-1.3.1.tar.gz
tar zxvf redmine-1.3.1.tar.gz
</pre>
<p>然后，我们登录到mysql上创建必要的数据库和用户。</p>
<pre class="brush: sql; gutter: true; ">
create database redmine character set utf8;
create user &#039;redmine&#039;@&#039;localhost&#039; identified by &#039;my_password&#039;;
grant all privileges on redmine.* to &#039;redmine&#039;@&#039;localhost&#039;;
</pre>
<p>然后，进入Redmine目录，把config/database.yml.example复制成config/database.yml，并根据刚才创建的数据库及用户修改这个文件：</p>
<pre class="brush: text; gutter: true;">
production:
  adapter: mysql
  database: redmine
  host: localhost
  username: redmine
  password: my_password
</pre>
<p>然后运行以下命令产生一个session store secret（存储密钥？）：</p>
<pre class="brush: bash; gutter: true;">
rake generate_session_store
</pre>
<p>回到应用根目录下（即redmine-1.3.1目录下），运行以下命令：</p>
<pre class="brush: bash; gutter: true;">
RAILS_ENV=production rake db:migrate
</pre>
<p>这个命令会创建必要的库表跟admin用户。</p>
<p>运行以下命令在数据库插入一些默认的设置数据。这个命令不是必须的。</p>
<pre class="brush: bash; gutter: true;">
RAILS_ENV=production rake redmine:load_default_data
</pre>
<p>运行后会要求你选择默认语言，选中文zh。</p>
<p>运行WEBrick来检验我们的安装，在应用根目录运行：</p>
<pre class="brush: bash; gutter: true;">
ruby script/server webrick -e production
</pre>
<p>我们在浏览器输入http://服务器ip:3000/来访问我们安装好的Redmine，管理员帐号/密码为admin/admin。</p>
<p>6. 修改Web Server<br />
由于WEBrick只是用来作测试用，并不适合用在生产环境上，所以我们需要更换一个Web Server，例如Mongrel。</p>
<pre class="brush: bash; gutter: true;">
gem install mongrel
</pre>
<p>安装好后，在应用根目录下运行：</p>
<pre class="brush: bash; gutter: true;">
ruby script/server mongrel -e production
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2012/03/linux-redmine-install/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[翻译]看图学Javascript Part II</title>
		<link>http://www.icyfire.me/2011/10/object-graphs-2/</link>
		<comments>http://www.icyfire.me/2011/10/object-graphs-2/#comments</comments>
		<pubDate>Mon, 31 Oct 2011 09:42:58 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1222</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/10/classical.png" alt="" title="classical" width="640" height="292" class="alignnone size-full wp-image-1227" />

第一篇使用图像描述Javascript语法的文章很受欢迎，所以我决定使用这个方法进行一些更加高级的想法。在这片文章里，我将会解释创建对象的三个常用技术。它们是使用原型的构造函数，纯原型以及对象工厂。

我的目标是想帮助大家明白每种技术的优点和弱点，并且明白它们具体是怎样运作的。 <a href="http://www.icyfire.me/2011/10/object-graphs-2/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<style>
#content .snippet dl:last-child {border-bottom-left-radius: 10px;border-bottom-right-radius: 10px;font-family: monaco,consolas,monospace;}
#content .snippet .output {background-color: #F0FFF0;}
#content .snippet dl {background-color: #F6F5F4;border: 1px solid #AAAAAA;font-size: 90%;margin: 0 0 12px 0;padding: 10px;}
#content .snippet dt {float: right;font-family: sans-serif;font-weight: bold; color: #779977;}
#content .snippet dd {margin-bottom:0;padding: 0;}
</style>
<p>第一篇使用图像描述Javascript语法的文章很受欢迎，所以我决定使用这个方法进行一些更加高级的想法。在这片文章里，我将会解释创建对象的三个常用技术。它们是使用原型的构造函数，纯原型以及对象工厂。</p>
<p>我的目标是想帮助大家明白每种技术的优点和弱点，并且明白它们具体是怎样运作的。</p>
<h2 style="font-weight:bold;color:#570E00;">经典的Javascript构造函数（Classical JavaScript Constructors）</h2>
<p>首先，我们用原型创建一个简单的构造函数。这是在原生的Javascript里你能找到的最接近类的东西。它很强大和高效，但是跟你所期待的其他语言的类比起来并不太一样。</p>
<pre class="brush: javascript; gutter: true">function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}
Rectangle.prototype.getArea = function getArea() {
  return this.width * this.height;
};
Rectangle.prototype.getPerimeter = function getPerimeter() {
  return 2 * (this.width + this.height);
};
Rectangle.prototype.toString = function toString() {
  return this.constructor.name + " a=" + this.getArea() + " p=" + this.getPerimeter();
};</pre>
<p>现在我们定义一个继承自Rectangle叫Square的类。要进行继承，构造函数的原型必须继承自父构造函数的原型。这里我们重写getPerimeter让它稍微高效点，同时也看看怎样重写一个函数。</p>
<pre class="brush: javascript; gutter: true">function Square(side) {
  this.width = side;
  this.height = side;
}
Square.prototype.__proto__ = Rectangle.prototype;
Square.prototype.getPerimeter = function getPerimeter() {
  return this.width * 4;
};</pre>
<p>用法很直接，分别创建一个实例并调用它们的函数。</p>
<pre class="brush: javascript; gutter: true">var rect = new Rectangle(6, 4);
var sqr = new Square(5);
console.log(rect.toString())
console.log(sqr.toString())</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">Rectangle a=24 p=20<br />Square a=25 p=20</div>
</dd>
</dl>
</div>
<p>下面是结果的数据结构，虚线表示对象的继承。</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/classical.png" alt="" title="classical" width="640" height="292" class="alignnone size-full wp-image-1227" /></p>
<p>注意到rect实例跟Square.prototype没啥不同。它们都是从Rectangle.prototype继承而来的简单对象。当你深入了解Javascript你会发现它只是一系列对象的连接。唯一比较特别的对象就是函数，它们可以接受参数和含有可执行的代码，并把它指向作用域。</p>
<h2 style="font-weight:bold;color:#570E00;">纯原型对象（Pure Prototypal Objects）</h2>
<p>让我们举些不使用构造函数的例子。这次我们只使用纯原型实现。</p>
<p>我们定义一个Rectangle原型，作为我们所有对象的一个模型。</p>
<pre class="brush: javascript; gutter: true">var Rectangle = {
  name: "Rectangle",
  getArea: function getArea() {
    return this.width * this.height;
  },
  getPerimeter: function getPerimeter() {
    return 2 * (this.width + this.height);
  },
  toString: function toString() {
    return this.name + " a=" + this.getArea() + " p=" + this.getPerimeter();
  }
};</pre>
<p>现在我们定义一个子对象Square并重写一些属性以改变一些特性。</p>
<pre class="brush: javascript; gutter: true">var Square = {
  name: "Square",
  getArea: function getArea() {
    return this.width * this.width;
  },
  getPerimeter: function getPerimeter() {
    return this.width * 4;
  },
};
Square.__proto__ = Rectangle;</pre>
<p>要创建这些原型的实例，我们只需要简单的创建继承自这些原型对象的新对象，然后设置它们的自己的属性。</p>
<pre class="brush: javascript; gutter: true">var rect = Object.create(Rectangle);
rect.width = 6;
rect.height = 4;
var square = Object.create(Square);
square.width = 5;
console.log(rect.toString());
console.log(square.toString());</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">Rectangle a=24 p=20<br />Square a=25 p=20</div>
</dd>
</dl>
</div>
<p>下面是结果的对象图。</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/prototypal.png" alt="" title="prototypal" width="640" height="243" class="alignnone size-full wp-image-1229" /></p>
<p>这个方法没有构造函数+原型的方法强大，但是它比较容易理解，因为它使用的继承比较少。而且如果你之前的语言也是使用这种纯原型的方式实现的话，那么你应该会觉得高兴Javascript也能这么干。</p>
<h2 style="font-weight:bold;color:#570E00;">对象工厂（Object Factories）</h2>
<p>我最喜欢的其中一个创建对象的方法就是使用工厂方法。它的不同之处在于它不用创建一个包含共享函数的原型对象然后创建实例，我只需要每次简单的调用一个函数然后返回一个新对象就行了。</p>
<p>这个例子是一个超级简单的MVC架构。Controller函数把Model跟View作为参数传入然后输出一个新的Controller对象。所有的状态都通过作用域保存在一个闭包里。</p>
<pre class="brush: javascript; gutter: true">function Controller(model, view) {
  view.update(model.value);
  return {
    up: function onUp(evt) {
      model.value++;
      view.update(model.value);
    },
    down: function onDown(evt) {
      model.value--;
      view.update(model.value);
    },
    save: function onSave(evt) {
      model.save();
      view.close();
    }
  };
}</pre>
<p>要使用它，只需要简单调用这个函数并传入你想传入的参数。看下我们是怎样直接把它们用作事件句柄（setTimeout）而不必先把函数绑定到对象上。而且它（函数）并没有在内部使用this，所以没有必要烦恼this的值这个问题了。</p>
<pre class="brush: javascript; gutter: true">var on = Controller(
  // Inline a mock model
  {
    value: 5,
    save: function save() {
      console.log("Saving value " + this.value + " somewhere");
    }
  },
  // Inline a mock view
  {
    update: function update(newValue) {
      console.log("View now has " + newValue);
    },
    close: function close() {
      console.log("Now hiding view");
    }
  }
);
setTimeout(on.up, 100);
setTimeout(on.down, 200);
setTimeout(on.save, 300);</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">View now has 5<br />View now has 6<br />View now has 5<br />Saving value 5 somewhere<br />Now hiding view</div>
</dd>
</dl>
</div>
<p>下面是这段代码的结果的对象图。注意到我们通过函数的隐藏[scope]属性访问了两个传入的匿名对象，换句话说，我们访问了用工厂方法创建的闭包里的Model和View。</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/factory.png" alt="" title="factory" width="640" height="244" class="alignnone size-full wp-image-1228" /></p>
<h2 style="font-weight:bold;color:#570E00;">总结</h2>
<p>我没有更多想要说的了，但是我想保持这篇文章短小精悍。如果有需要的话，我会写第三篇讲解如何写ruby式的Mixin以及其他进阶的话题。</p>
<p>原文：<a href="http://howtonode.org/object-graphs-2" target="_blank">http://howtonode.org/object-graphs-2</a></p>
<p>Translated by icyfire @ 2011-10-31</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/10/object-graphs-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[翻译]看图学Javascript Part I</title>
		<link>http://www.icyfire.me/2011/10/object-graphs-1/</link>
		<comments>http://www.icyfire.me/2011/10/object-graphs-1/#comments</comments>
		<pubDate>Fri, 28 Oct 2011 11:01:42 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1160</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/09/aa776_functions.png" alt="" title="函数" width="544" height="393" class="alignnone size-full wp-image-1174" />

要想成为一个出色的Javascript开发者，其中一个秘密就是真正的去理解这门语言。这篇文章将会用一些简单易懂的图表来解释Javasript的一些基本要素。 <a href="http://www.icyfire.me/2011/10/object-graphs-1/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<style>
#content .snippet dl:last-child {border-bottom-left-radius: 10px;border-bottom-right-radius: 10px;font-family: monaco,consolas,monospace;}
#content .snippet .output {background-color: #F0FFF0;}
#content .snippet dl {background-color: #F6F5F4;border: 1px solid #AAAAAA;font-size: 90%;margin: 0 0 12px 0;padding: 10px;}
#content .snippet dt {float: right;font-family: sans-serif;font-weight: bold; color: #779977;}
#content .snippet dd {margin-bottom:0;padding: 0;}
</style>
<p>要想成为一个出色的Javascript开发者，其中一个秘密就是真正的去理解这门语言。这篇文章将会用一些简单易懂的图表来解释Javasript的一些基本要素。</p>
<h2 style="font-weight:bold;color:#570E00;">无处不在的引用（References Everywhere）</h2>
<p>Javascript里的变量简单来说就是一个引用了内存里某个值的标签。这个值可以是字符串，数字或者布尔值这些基本元素，也可以是对象或者函数。</p>
<h3 style="font-weight:bold;color:#570E00;">局部变量（Local Variables）</h3>
<p>在下面的例子里，我们会在顶级作用域里创建四个局部变量然后把它们指向一些基本类型的值：</p>
<pre class="brush: javascript; gutter: true">
// Let's create some local variables in the top scope
var name = "Tim Caswell";
var age = 28;
var isProgrammer = true;
var likesJavaScript = true;
// Test to see if the two variables reference the same value
isProgrammer === likesJavaScript;
</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">=&gt; true</div>
</dd>
</dl>
</div>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/09/2d2c2_variables.png" alt="" title="局部变量" width="323" height="203" class="alignnone size-full wp-image-1170" /></p>
<p>注意到两个布尔型变量指向了内存里的同一个值。这是因为基本元素是不可变的（immutable），解析器可以进行优化，对这些值的引用分享同一个实例。<br />
在这段代码中，我们用 === 来检验两个引用是不是指向同一个值，结果返回是true。<br />
在外层的代码处于最外层的闭包作用域（The outer box represents the outermost closure scope.），这些变量是顶级的局部变量，不要跟global/window对象的属性相混淆。</p>
<h3 style="font-weight:bold;color:#570E00;">对象与原型链（Objects and Prototype Chains）</h3>
<p>对象就是一个集合，包含了更多的对新对象与原型的引用。唯一特别的地方是当你试图访问一个在父对象里而不在局部对象里的属性时所用到的原型链。</p>
<pre class="brush: javascript; gutter: true">
// Create a parent object
var tim = {
  name: "Tim Caswell",
  age: 28,
  isProgrammer: true,
  likesJavaScript: true
}
// Create a child object
var jack = Object.create(tim);
// Override some properties locally
jack.name = "Jack Caswell";
jack.age = 4;
// Look up stuff through the prototype chain
jack.likesJavaScript;
</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">=&gt; true</div>
</dd>
</dl>
</div>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/09/91909_objects.png" alt="对象" title="对象" width="611" height="337" class="alignnone size-full wp-image-1173" /></p>
<p>这里我们有一个具有四个属性的对象，被变量tim所引用。同时我们创建了一个由第一个对象继承而来的新对象，被变量jack所引用。然后我们重写这个局部对象中的两个属性。<br />
当我们要访问jack.likesJavaScript的时候，我们先会查找jack所引用的对象，然后在其中查找likesJavaScript这个属性。对象里没有这个属性，于是我们在父对象里进行查找并找到了它，并知道它引用了值true。</p>
<h3 style="font-weight:bold;color:#570E00;">全局对象（The Global Object）</h3>
<p>曾经有疑问为什么像jslint这些工具老是告诉你不要忘记用var定义变量，好吧，让我告诉你如果你忘记了的话。</p>
<pre class="brush: javascript; gutter: true">
var name = "Tim Caswell";
var age = 28;
var isProgrammer = true;
// Oops we forgot a var
likesJavaScript = true;
</pre>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/09/7857c_globals.png" alt="" title="全局对象" width="440" height="246" class="alignnone size-full wp-image-1172" /></p>
<p>注意到likesJavaScript现在是全局对象的一个属性而不是外层闭包里的一个变量。当你把多段代码混合在一起就可能会出现问题，而事情你确实会经常这么做。<br />
总是记得加上var使你的变量的作用域处于当前的闭包和子闭包里。你当然会很乐意遵循这个这么简单的规则。<br />
如果你必须把东西放到全局对象上，你可以在浏览器上使用window.woo或者在node.js使用global.goo。</p>
<h2 style="font-weight:bold;color:#570E00;">函数与闭包（Functions and Closures）</h2>
<p>Javascript不仅仅是一系列的数据结构链。它包含了可执行和调用的代码，这种代码被称作函数。这些函数会创建链式作用域和闭包。</p>
<h3 style="font-weight:bold;color:#570E00;">观察闭包（Visualizing Closures）</h3>
<p>函数可以看作是特别的对象，对象包含了可执行的代码和属性。每个函数在定义时都会赋予一个特别的[scope]属性表明它正处于哪个环境。如果一个函数是在另外一个函数里被返回，那么对旧环境的引用就会被新的函数关闭在“闭包”里。<br />
在这个例子里我们会创建一个简单的工厂方法来产生一个闭包，并返回一个函数。</p>
<pre class="brush: javascript; gutter: true">
function makeClosure(name) {
  return function () {
    return name;
  };
}
var description1 = makeClosure("Cloe the Closure");
var description2 = makeClosure("Albert the Awesome");
console.log(description1());
console.log(description2());
</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">Cloe the Closure<br />Albert the Awesome</div>
</dd>
</dl>
</div>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/09/7857c_closure-640x340.png" alt="" title="闭包" width="640" height="340" class="alignnone size-medium wp-image-1171" /></p>
<p>当我们调用description1()的时候，VM就会查找他引用的函数然后执行它。而这个函数会在闭包的作用域里去找一个叫做name的局部变量。这是个不错的工厂方法，每个产生的函数都有自己存放局部变量的空间。</p>
<p>想深入了解闭包和它的用法，可以看<a href="http://howtonode.org/why-use-closure" target="_blank">why use closure</a>这篇文章。</p>
<h3 style="font-weight:bold;color:#570E00;">共享函数与this（Shared Functions and this）</h3>
<p>有时为了性能上的考虑，或者你纯粹喜欢这样，Javascript提供了this这个关键字让你在不同的作用域里重用函数对象，这取决于你是怎样调用它的。</p>
<p>这里我们创建了一些对象，这些对象共享了同一个函数。这个函数将会在内部引用this，向你展示它在不同的调用里会有哪些不同的表现。</p>
<pre class="brush: javascript; gutter: true">
var Lane = {
  name: "Lane the Lambda",
  description: function () {
    return this.name;
  }
};
var description = Lane.description;
var Fred = {
  description: Lane.description,
  name: "Fred the Functor"
};
// Call the function from four different scopes
console.log(Lane.description());
console.log(Fred.description());
console.log(description());
console.log(description.call({
  name: "Zed the Zetabyte"
}));
</pre>
<div class="snippet">
<dl class="output">
<dt>输出</dt>
<dd>
<div class="last-expression">Lane the Lambda<br />Fred the Functor<br />undefined<br />Zed the Zetabyte</div>
</dd>
</dl>
</div>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/09/aa776_functions.png" alt="" title="函数" width="544" height="393" class="alignnone size-full wp-image-1174" /></p>
<p>在这种图表里，我们看到就算Fred.description被设为Lane.description，它事实上只是引用那个函数。因此三个引用都是引用相同的匿名函数。因此我尽量不会用构造函数原型这个“方法”去调用函数，因为这样意味着会把这个函数绑定到构造函数以及他的“类”上（This is why I try to not call functions on constructor prototypes "methods", because that implies some sort of binding of the function to the constructor and it's "class"）。（想知道更多关于this的动态性质请看<a href="http://howtonode.org/what-is-this" target="_blank">what is this</a>）</p>
<h2 style="font-weight:bold;color:#570E00;">总结</h2>
<p>使用图表去表现这些数据结构是件很有趣的事情。我希望这个能帮助像我们一样通过视觉学习（visual learner）的人更好的掌握Javascript的语法。我具有前端设计/开发以及后台开发的经验，我希望我独特的视觉能给那些从事设计以及正在深入学习Javascript这个美妙的语言的人带来帮助。</p>
<p>(所有图表都是<a href="http://www.graphviz.org/" target="_blank">graphviz</a>的点文件（dot file），可以在<a href="http://github.com/creationix/howtonode.org/tree/master/articles/object-graphs/" target="_blank">这里</a>查看)</p>
<p>原文：<a href="http://howtonode.org/object-graphs" title="Learning Javascript with Object Graphs" target="_blank">http://howtonode.org/object-graphs</a></p>
<p>Translated by icyfire @ 2011-09-02 updated @ 2011-10-28</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/10/object-graphs-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>编写第一个Django应用：第二部分</title>
		<link>http://www.icyfire.me/2011/10/writing-your-first-django-app-part-2/</link>
		<comments>http://www.icyfire.me/2011/10/writing-your-first-django-app-part-2/#comments</comments>
		<pubDate>Wed, 26 Oct 2011 11:31:39 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[程序语言]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1134</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/08/django.jpg" alt="Django" />

承接第一部分，第二部分主要讲Django自动生成的admin系统。

<img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_01.jpg" alt="" title="Django admin" width="640" height="236" class="alignnone size-full wp-image-1189" /> <a href="http://www.icyfire.me/2011/10/writing-your-first-django-app-part-2/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<style type="text/css">
#content p {margin-bottom:10px;}
#content .djg-c { border: 1px solid #94DA3A; margin: 1em 0; padding: 0.8em 1em 0.8em 65px;padding-left: 65px;background: url("http://www.icyfire.me/wp-content/uploads/2011/08/docicons-note.gif") no-repeat scroll 0.8em 0.8em transparent;}
#content .djg-c-t {color: #487858;font-weight: bold;margin:0;}
#content .djg-c-c {margin: 10px 0 0;}
#content .djg-title {color:#487858;}
#content .djg-pre {color:#234F32;}
#content .djg-link {color:#AB5603;text-decoration:none;}
#content .djg-link2 {color:#234F32;text-decoration:none;border-bottom: 1px #234F32 dotted;}
</style>
<p>第二部分将会继续完成第一部分剩余的内容。我们会继续我们的Poll应用以及聚焦到Django自动生成的admin页面。</p>
<div class="djg-c">
<p class="djg-c-t">原理</p>
<p class="djg-c-c">为你的员工和客户产生admin网页来管理内容是件乏味和无需创造力的事情。因此，Django为模型全自动的创建了admin界面。</p>
<p class="djg-c-c">Django是在“内容发布者”页面和“公共”页面分界很清晰的这样一个环境下编写的，网站的管理者使用这个系统添加新闻，事件，比赛比分等等的内容，然后这些内容会显示在公共网页上。Django提供了一个统一的界面让管理员编辑内容。</p>
<p class="djg-c-c">admin功能对网页访问者来说不是必要的，它是给管理员用的。</p>
</div>
<h2 class="djg-title" id="s-activate-the-admin-site">激活admin网页（Activate the admin site）</h2>
<p>Django的admin页面默认是没有激活的，要想激活它，你需要做三件事情：</p>
<ul>
<li>添加<span class="djg-pre">"django.contrib.admin"</span>到你的<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>设置里。</li>
<li>运行<span class="djg-pre">python manage.py syncdb</span>。既然你已经添加了一个新的应用到<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>里，数据库里的表自然也需要更新。</li>
<li>编辑文件<span class="djg-pre">mysite/urls.py</span>反注释跟admin有关的行 —— 一共有三行需要注释。这个文件是一个url配置文件，我们将会在下一个教程深入URL的配置。现在，你所需要知道的就是它会把URL映射到应用上。 做完这些后，文件<span class="djg-pre">urls.py</span>应该类似下面这样：
<pre class="brush: python; gutter: false">from django.conf.urls import patterns, include, url

# 反注释下两行开启admin功能：
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # 例子：
    # url(r'^$', '{{ project_name }}.views.home', name='home'),
    # url(r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),

    # 反注释admin/doc这一行开启管理文档url：
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # 反注释下一行开启管理页面url：
    url(r'^admin/', include(admin.site.urls)),
)</pre>
<p>(需要反注释的是行4，5，16。)</li>
</ul>
<h2 class="djg-title" id="s-start-the-development-server">启动开发服务器（Start the development server）</h2>
<p>我们启动开发用服务器，然后打开admin页面。</p>
<p>还记得教程1里你这样启动开发用服务器吧：</p>
<pre class="brush: shell; gutter: false">python manage.py runserver</pre>
<p>现在，打开浏览器访问你本地域名上的"/admin/" —— 例如，<a href="http://127.0.0.1:8000/admin/" target="_blank" class="djg-link">http://127.0.0.1:8000/admin/</a>。你应该可以看到admin的登录界面：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_01.jpg" alt="" title="Django admin" width="640" height="236" class="alignnone size-full wp-image-1189" /></p>
<h2 class="djg-title" id="s-enter-the-admin-site">进入admin页面（Enter the admin site）</h2>
<p>现在我们试试登录。（你在教程1里已经创建过一个超级用户帐号，还记得吗？如果你没有或者忘记了密码，你可以<a href="https://docs.djangoproject.com/en/dev/topics/auth/#topics-auth-creating-superusers" target="_blank" class="djg-link">再创建一个</a>。）登录后你会看到Django的admin首页：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_021.jpg" alt="" title="django_admin_02" width="640" height="163" class="alignnone size-full wp-image-1191" /></p>
<p>你应该会看到一些其他的可编辑内容，包括群组，用户和网站。这些核心的功能默认包含在Django里。</p>
<h2 class="djg-title" id="make-the-poll-app-modifiable-in-the-admin">让poll应用可修改（Make the poll app modifiable in the admin）</h2>
<p>但是我们的poll应用在哪呢？它没有在admin的首页显示。</p>
<p>我们只需做一件事：告诉admin，<span class="djg-pre">Poll</span>对象需要一个admin界面。在你的<span class="djg-pre">polls</span>文件夹里创建一个名字叫<span class="djg-pre">admin.py</span>的文件，然后编辑成以下样子：</p>
<pre class="brush: python; gutter: false">from polls.models import Poll
from django.contrib import admin

admin.site.register(Poll)</pre>
<p>要看到这些修改生效，你需要重启开发用服务器。通常，当你修改文件后服务器会自动重载代码，但是创建一个新文件并不会触发自动重载。</p>
<h2 class="djg-title" id="s-explore-the-free-admin-functionality">admin功能探索（Explore the free admin functionality）</h2>
<p>现在我们已经注册好<span class="djg-pre">Poll</span>了，Django知道要把它显示在admin首页上。</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_03.jpg" alt="" title="django_admin_03" width="640" height="204" class="alignnone size-full wp-image-1192" /></p>
<p>点击"Polls"。现在你在polls的"change list"页面上。这个页面显示了数据库里所有的poll，你可以选择其中一个进行编辑。我们可以看到我们教程1里创建的"What's up?" poll：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_04.jpg" alt="" title="django_admin_04" width="640" height="155" class="alignnone size-full wp-image-1193" /></p>
<p>点击"What's up?"进行编辑：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_05.jpg" alt="" title="django_admin_05" width="640" height="172" class="alignnone size-full wp-image-1195" /></p>
<p>这里需要注意几点：</p>
<ul>
<li>表格是根据Poll模型自动生成的。</li>
<li>不同模型字段类型(<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateTimeField" target="_blank" class="djg-link2">DateTimeField</a>，<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.CharField" target="_blank" class="djg-link2">CharField</a>)对应到相应的HTML的input组件。 Django的admin知道每种类型的字段该怎么显示。</li>
<li>每个<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateTimeField" target="_blank" class="djg-link2">DateTimeField</a>都有一些Javascript快捷操作功能。日期有"Today"快捷操作和弹出的日历，时间有"Now"快捷操作和列出常用时间输入的弹出窗口。</li>
</ul>
<p>页面的底部有一些选项：</p>
<ul>
<li>保存 -- 保存修改并返回到这类型对象的修改列表页面。</li>
<li>保存并继续编辑 -- 保存修改并重新加载这个对象的admin页面。</li>
<li>保存并添加新记录 -- 保存修改并加载一个这个类型对象的新的空白表格。</li>
<li>删除 -- 显示一个确认删除的页面。</li>
</ul>
<p>点击"Today"和"Now"修改"Date published"，然后点击"Save and continue editing"。再点击右上的"History"，你会看到一个页面列出了这个对象所有的通过admin的修改历史，包括修改时间和修改者等信息：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_06-640x126.jpg" alt="" title="django_admin_06" width="640" height="126" class="alignnone size-medium wp-image-1197" /></p>
<h2 class="djg-title" id="s-customize-the-admin-form">自定义admin表单（Customize the admin form）</h2>
<p>花几分钟时间去感叹下你少写了多少代码！通过用<span class="djg-pre">admin.site.register(Poll)</span>注册Poll模型，Django可以构建一个默认的表单显示出来。通常你会想自定义admin表单的样子和功能，这时你可以在注册对象的时候告诉Django你想要的选项。</p>
<p>让我们通过重新排列编辑表单的字段顺序来看看这是怎么做到的。把<span class="djg-pre">admin.site.register(Poll)</span>这一行换成：</p>
<pre class="brush: python; gutter: false">class PollAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question']

admin.site.register(Poll, PollAdmin)</pre>
<p>你需要使用这个模式 —— 在你想修改一个对象的admin选项时，先创建一个模型的admin对象，然后作为第二个参数传进<span class="djg-pre">admin.site.register()</span>里。</p>
<p>上面的修改使"Publication date"位于"Question"字段的前面：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_07.jpg" alt="" title="django_admin_07" width="640" height="220" class="alignnone size-full wp-image-1199" /></p>
<p>就两个字段的话没什么大不了，但是如果是对于有几十个字段的表单来说，选择更为直观的顺序对可用性来说是一个重要的细节。</p>
<p>说到有几十个字段的表格，你可能会想把表格的字段分组：</p>
<pre class="brush: python; gutter: false">class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date']}),
    ]

admin.site.register(Poll, PollAdmin)</pre>
<p>在fieldsets里，元组（tuple）的第一个元素是fieldset的标题。现在我们的表格看起来会像这样：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_08.jpg" alt="" title="django_admin_08" width="640" height="241" class="alignnone size-full wp-image-1200" /></p>
<p>你可以给fieldset分配任意的HTML类名。Django提供了一个<span class="djg-pre">"collapse"</span>类，使特定的一个fieldset在初始时闭合显示。当你有一个很长的表单包含若干不常用的字段时，这个类就会很有用。</p>
<pre class="brush: python; gutter: false">class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]</pre>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_09.jpg" alt="" title="django_admin_09" width="640" height="226" class="alignnone size-full wp-image-1201" /></p>
<h2 class="djg-title" id="adding-related-objects">添加关联对象（Adding related objects）</h2>
<p>好了，我们有了Poll的管理页面了。但是一个<span class="djg-pre">Poll</span>有多个<span class="djg-pre">Choices</span>，而管理页面并没有显示choice。</p>
<p>有两个方法解决这个问题。第一个方法是在admin那注册一个<span class="djg-pre">Choice</span>，就跟我们刚才注册<span class="djg-pre">Poll</span>一样。这个很简单：</p>
<pre class="brush: python; gutter: false">from polls.models import Choice

admin.site.register(Choice)</pre>
<p>现在"Choices"选项已经出现在Django的admin里了。"Add choice"表单看起来就像这样：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_10.jpg" alt="" title="django_admin" width="640" height="272" class="alignnone size-full wp-image-1213" /></p>
<p>在这个表单里，"Poll"字段是一个下拉框，它包含了数据库里所有的poll。Django知道应该把外键（<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey" target="_blank" class="djg-link2">ForeignKey</a>）呈现为一个下拉框。所以在我们这里，下拉框只有一个poll。</p>
<p>注意到"Poll"旁边有个"Add Another"链接。每一个用<span class="djg-pre">外键</span>关联的对象都有这么一个链接。当年点击"Add Another"的时候，将会弹出一个包含"Add poll"表单的窗口。如果你在这个窗口添加了一个poll并点击"Save"，Django就会把这个poll保存到数据库里并动态的把它加到下拉框里。</p>
<p>但是事实上，这是一个很低效的添加Choice对象的方法。如果能直接在创建Poll对象时添加一些Choice，这才是更好的方法。让我们来实现它吧。</p>
<p>把为Choice模型调用的<span class="djg-pre">register()</span>删掉，然后编辑注册Poll的代码：</p>
<pre class="brush: python; gutter: false">class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3

class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Poll, PollAdmin)</pre>
<p>这段代码告诉Django：“Choice对象放到Poll的管理页面里进行编辑。页面默认提供足够添加3个choice的字段。”</p>
<p>打开"Add poll"页面看看效果，你可能需要重启开发服务器：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_11.jpg" alt="" title="django_admin_11" width="640" height="486" class="alignnone size-full wp-image-1214" /></p>
<p>它是这样工作的：在页面上有三行空间用来关联Choice——由<span class="djg-pre">extra</span>指定。每次你回到一个已经创建好的对象的"Change"页面时，除了已经存在的关联的choice外，你还可以看到另外三个额外的空间用来创建新的Choice。</p>
<p>还有一个小问题，要显示这三个用来关联Choice对象的所有字段，需要霸占很大的屏幕空间，因此，Django提供了一个表格方式来显示这些关联的对象。你只需要把ChoiceInline改成下面这样：</p>
<pre class="brush: python; gutter: false">class ChoiceInline(admin.TabularInline):
    #...</pre>
<p>使用TabularInline（而不是StackedInline）后，关联的对象就会以一个更紧凑的表格式显示出来：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_12.jpg" alt="" title="django_admin_12" width="640" height="331" class="alignnone size-full wp-image-1215" /></p>
<h2 class="djg-title" id="customize-the-admin-change-list">自定义修改列表页面（Customize the admin change list）</h2>
<p>现在Poll管理页面看起来还不错，让我们对"change list"页面——显示所有poll的那个页面做点调整。</p>
<p>现在它是这个样子的：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_13.jpg" alt="" title="django_admin_13" width="640" height="195" class="alignnone size-full wp-image-1216" /></p>
<p>Django默认会调用每个对象的<span class="djg-pre">str()</span>作显示。但是有时我们会想显示某些个别更有用的字段。这个时候，我们需要用到<span class="djg-pre">list_display</span>这个选项，它是一个要显出的字段名字的元组（tuple），这些名字将会作为列名显示在对象的修改列表页面上。</p>
<pre class="brush: python; gutter: false">class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date')</pre>
<p>Just for good measure, let's also include the was_published_today custom method from Tutorial 1:</p>
<pre class="brush: python; gutter: false">class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date', 'was_published_today')</pre>
<p>现在poll的修改列表页面看起来会是这样：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_14.jpg" alt="" title="django_admin_14" width="640" height="195" class="alignnone size-full wp-image-1217" /></p>
<p>你可以点击的列头进行排序——除了<span class="djg-pre">was_published_today</span>那一列，因为现在还不支持对方法输出的值进行排序。同时还需要注意的是，<span class="djg-pre">was_published_today</span>那一列的列头的名字默认是方法的名字（下划线会用空格代替）。但是你可以通过给方法（在<span class="djg-pre">models.py</span>里）一个<span class="djg-pre">short_description</span>属性来修改它。</p>
<pre class="brush: python; gutter: false">def was_published_today(self):
    return self.pub_date.date() == datetime.date.today()
was_published_today.short_description = 'Published today?'</pre>
<p>再次编辑admin.py对Poll的修改列表页面加个功能：过滤（Filters）。在<span class="djg-pre">PollAdmin</span>里增加如下代码：</p>
<pre class="brush: python; gutter: false">list_filter = ['pub_date']</pre>
<p>它会增加一个"Filter"侧边栏让用户可以通过pub_date字段对修改列表进行过滤：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/django_admin_15.jpg" alt="" title="django_admin_15" width="640" height="234" class="alignnone size-full wp-image-1218" /></p>
<p>显示的过滤类型取决于要进行过滤的字段的类型。因为pub_date是一个DateTimeField，Django知道默认需要给DateTimeFields这些过滤选项："Any date"， "Today"， "Past 7 days"， "This month"和"This year"。</p>
<p>目前情况进展的还不错。让我们添加一些搜索的功能：</p>
<pre class="brush: python; gutter: false">search_fields = ['question']</pre>
<p>它会在修改列表顶部增加一个搜索框。当输入一些搜索词，Django会去搜索<span class="djg-pre">question</span>字段。只要你喜欢，你可以使用任意多的字段——但是因为它背后是使用<span class="djg-pre">LIKE</span>进行查询，所以尽量合理一点，以免你的数据库不高兴。</p>
<p>最后，因为Poll对象有日期，所以如果能根据日期进行筛选就会显得比较方便。增加以下代码：</p>
<pre class="brush: python; gutter: false">date_hierarchy = 'pub_date'</pre>
<p>它会在修改列表页面的顶部根据日期增加一个层级性导航。在最顶层他会显示所有有效的年份，然后逐层深入到月份和天。</p>
<p>修改列表页面自带分页功能，默认每页显示50条记录。分页，搜索框，过滤，日期层级和排序，它们就像你所期待的那样一起运作着。</p>
<h2 class="djg-title" id="customize-the-admin-look-and-feel">自定义管理界面（Customize the admin look and feel）</h2>
<p>很明显，在每个管理页面的顶部显示Django administratio是很荒谬的，它只是个占位用文本。</p>
<p>但这个使用Django的模板系统很容易就能修改。Django的管理系统是使用Django自身实现的，它的界面使用的是Django本事的模板系统。</p>
<p>打开配置文件（<span class="djg-pre">mysite/settings.py</span>）并找到<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">TEMPLATE_DIRS</a>这个设置。<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">TEMPLATE_DIRS</a>是一个文件系统路径的元组（tuple），在加载Django模板时会对它进行检查。它是一个搜索路径。</p>
<p><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">TEMPLATE_DIRS</a>默认是空的。所以，我们增加一行代码进去，告诉Django我们的模板放在哪：</p>
<pre class="brush: python; gutter: false">TEMPLATE_DIRS = (
    '/home/my_username/mytemplates', # 这里改为你自己的路径。
)</pre>
<p>现在把模板文件<span class="djg-pre">admin/base_site.html</span>从Django源码(<span class="djg-pre">django/contrib/admin/templates</span>)的admin模板目录里复制到你在<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">TEMPLATE_DIRS</a>里设置的目录里的一个子目录admin。例如，如果你的<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">TEMPLATE_DIRS</a>包含'<span class="djg-pre">/home/my_username/mytemplates</span>'，就像上面设置一样，那么就复制<span class="djg-pre">django/contrib/admin/templates/admin/base_site.html</span>到<br />
<span class="djg-pre">/home/my_username/mytemplates/admin/base_site.html</span>。别忘了admin这个子目录。</p>
<p>然后，编辑那个模板文件，并把Django那段通用文本替换成你想要显示的网站名。</p>
<p>这个模板文件包含了很多像<span class="djg-pre">{% block branding %}</span>和<span class="djg-pre">{{ title }}</span>这样的文本。<span class="djg-pre">{%</span>和<span class="djg-pre">{{</span>标签是Django模板语言中的一部分。当Django渲染<span class="djg-pre">admin/base_site.html</span>时，这个模板语言将会被评估产生出最后的HTML页面。如果你现在对模板还没啥概念的话，别担心，我们将在教程3里深入Django的模板语言。</p>
<p>所有的Django的默认admin模板都可以被重写，要重写一个模板，你只需要重复刚才操作<span class="djg-pre">base_site.html</span>的步骤——从默认目录复制到你自定义的目录里并修改。</p>
<p>精明的读者也许会问：如果<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">TEMPLATE_DIRS</a>默认为空，Django是怎样找到默认的admin模板的？答案是，Django默认会自动在每个app包里寻找<span class="djg-pre">templates/</span>这个子目录。想知道全部信息，可以看<a href="https://docs.djangoproject.com/en/dev/ref/templates/api/#template-loaders" target="_blank" class="djg-link">template loader documentation</a>。</p>
<h2 class="djg-title" id="customize-the-admin-index-page">自定义管理首页（Customize the admin index page）</h2>
<p>基于近似的理由，你可能像自定义Django管理系统的首页。</p>
<p>在默认情况下，admin会显示注册过的所有在<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_DIRS" target="_blank" class="djg-link2">INSTALLED_APPS</a>设置的app，并按字母进行排序。你也许想要把界面弄得更有意义，毕竟，首页可能是admin里最重要的页面了，它应该做到易于使用。</p>
<p>需要自定义的模板是<span class="djg-pre">admin/index.html</span>。（像前一部分对<span class="djg-pre">admin/base_site.html</span>做的那样——把它从默认目录复制到你的自定义模板目录里。）编辑这个文件，你会看到它使用了一个模板变量叫<span class="djg-pre">app_list</span>。这个变量包含了所有已经安装的Django app。如果你不想使用它，你可以在你想要放置的位置硬编码链接到对象相应的admin页面。再次的，如果你搞不懂模板语言，我们将会在教程3进行详情讲解。</p>
<p>如果你对管理网站感觉良好，那么可以开始关于poll公共视图的教程的第三部分了。</p>
<p>教程第二部分原文：<a href="https://docs.djangoproject.com/en/dev/intro/tutorial02/" target="_blank">https://docs.djangoproject.com/en/dev/intro/tutorial02/</a></p>
<p>Written by icyfire @ 2011.09.26 updated @ 2011.10.26</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/10/writing-your-first-django-app-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>本地存储浅析</title>
		<link>http://www.icyfire.me/2011/10/local-storage-brief/</link>
		<comments>http://www.icyfire.me/2011/10/local-storage-brief/#comments</comments>
		<pubDate>Tue, 18 Oct 2011 11:47:25 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[Cookie]]></category>
		<category><![CDATA[Gears]]></category>
		<category><![CDATA[Shared Object]]></category>
		<category><![CDATA[userData]]></category>
		<category><![CDATA[Web Storage]]></category>
		<category><![CDATA[本地存储]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1175</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/09/20110515054811428.jpg" alt="" title="本地存储" width="600" height="202" class="alignnone size-full wp-image-1181" />

相比于桌面应用，Web应用在本地存储方面确实显得有点力不从心。在桌面应用，你可以使用INI或者XML等文件来保存配置和数据，甚至可以使用内嵌小型数据库的方法去保存数据，而对于网站来说，在很长的一段时间里，我们只能使用Cookies这个充满缺点但又无法替代的东西。不过随着互联网的发展，本地存储技术也一直在发展，但始终没有出现一个能满足需求的技术，直到Web Storage的出现。

下面我们来看下一些比较常见的本地存储技术。 <a href="http://www.icyfire.me/2011/10/local-storage-brief/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<style>
#local-storage a {text-decoration:none;}
#local-storage p, #local-storage ul {margin-bottom:18px;}
#local-storage .feature {vertical-align: middle;}
</style>
<div id="local-storage">
<img src="http://www.icyfire.me/wp-content/uploads/2011/09/20110515054811428.jpg" alt="" title="本地存储" width="600" height="202" class="alignnone size-full wp-image-1181" /></p>
<p>相比于桌面应用，Web应用在本地存储方面确实显得有点力不从心。在桌面应用，你可以使用INI或者XML等文件来保存配置和数据，甚至可以使用内嵌小型数据库的方法去保存数据，而对于网站来说，在很长的一段时间里，我们只能使用Cookies这个充满缺点但又无法替代的东西。不过随着互联网的发展，本地存储技术也一直在发展，但始终没有出现一个能满足需求的技术，直到Web Storage的出现。</p>
<p>下面我们来看下一些比较常见的本地存储技术。</p>
<h2 style="border-bottom:2px solid #487858;">Cookies</h2>
<p>这个由Netscape的前雇员Lou Montulli在1993年3月的发明的东西，至今仍然被广泛的应用在各网站上，我们用它来保存登录状态，用它来保存浏览过的商品，用它来保存购物车物品，等等。</p>
<p>目前cookie存在了以下几种的规范：</p>
<ul>
<li><a href="http://curl.haxx.se/rfc/cookie_spec.html" target="_blank">Netscape cookie草案</a>：是最早的cookie规范，也是RFC2109的基础。尽管如此，它与RFC2109还是有较大的差别，所以跟一些服务器有兼容上的问题。 </li>
<li><a href="http://www.ietf.org/rfc/rfc2109.txt" target="_blank">RFC2109</a>：基于是w3c发布的第一个官方cookie规范。理论上，所有的服务器在处理版本1的cookie时，都要遵循此规范。遗憾的是，由于这个规范过于严格，很多服务器并没有正确的实现这个标准或者还是在使用Netscape的cookie草案。</li>
<li><a href="http://www.ietf.org/rfc/rfc2965.txt" target="_blank">RFC2965</a>：cookie规范的第二个版本，指出了RFC2109里cookie版本1的不足。RFC2965的目标是最终取代RFC2109。</li>
</ul>
<p>想深入了解Cookie可以研究下这些规范，但这篇文章里并不会再深入进去，这里列出几个规范除了扫盲外主要是为了解释一个问题，什么是cookie？Netscape的cookie草案里是这么说的：</p>
<blockquote><p>A server, when returning an HTTP object to a client, may also send a piece of state information which the client will store. Included in that state object is a description of the range of URLs for which that state is valid. Any future HTTP requests made by the client which fall in that range will include a transmittal of the current value of the state object from the client back to the server. The state object is called a cookie, for no compelling reason.</p></blockquote>
<p>下面我们来看看浏览器，web服务器跟cookie之间的交互是怎样的：</p>
<ul>
<li>当我们用浏览器访问一个网站的时候，就会想服务器发起一个请求。</li>
<li>根据请求的url和cookie本身的属性，筛选出需要发送给服务器的cookie（当然也有可能没有cookie）。</li>
<li>如果有多个cookie的话，还会决定发送的顺序。</li>
<li>把这些需要发送的cookie包含在HTTP的包头里发送给服务器。</li>
<li>然后到了应答阶段，服务器会发回一个应答包头，包头里包含了cookie信息。</li>
<li>浏览器解析这个cookie，包括名称，值，路径等元素。</li>
<li>最后，把cookie保存在本地。</li>
</ul>
<p>至于哪些cookie会被发送到服务器端，是有一套规则的，例如域名选择、路径选择和Max-Age选择，这些都可以在<a href="http://www.ietf.org/rfc/rfc2109.txt" target="_blank">RFC2109</a>里找到，这里就不展开了。</p>
<p>从上面可以看到，每次的http请求，cookie都会包含在包头里发送给服务器，这也是被开发者广为诟病的一个cookie缺点，因为这意味这每个请求都无形中增加了流量，特别是像请求图片这些资源的时候，附带的cookie信息是完全没有必要的。所以现在很多网站图片等静态资源都会用独立的域名运作，这样就可以单独对这些域名进行cookie设置。<br />
除此以外，cookie还有以下影响比较大的缺点：</p>
<ul>
<li>安全性问题。cookie在http请求中是明文传递的，除非使用SSL通道，不然是不宜在cookie里放置重要信息。</li>
<li>大小问题。每个cookie的大小不能超过4K。每个Domain下cookie个数根据浏览器不同也不同。</li>
</ul>
<p>关于Cookies的一些限制问题，可以参考下Nicholas的一篇<a href="http://www.nczonline.net/blog/2008/05/17/browser-cookie-restrictions/" target="_blank">文章</a>：<br />
浏览器允许的每个域名下的Cookie数：</p>
<ul>
<li>IE7跟IE8限制为50个。</li>
<li>Firefox限制为50个。</li>
<li>Opera限制30个</li>
<li>Safari/WebKit没有限制，但是如果header的大小超过服务器能处理的情况下，则会出现错误。</li>
</ul>
<p>那如果Cookie数设置超过限制的时候，各浏览器又是如何处理呢：</p>
<ul>
<li>Safari由于没有Cookie数的限制，所以不作讨论。</li>
<li>在IE和Opera下，将会采用LRU（The Least Recently Used）方法，自动踢掉最老的cookie以腾出空间给新的cookie。IE和Opera使用这个方法。</li>
<li>Firefox就比较独特：它貌似会随机决定哪些cookie将会保留，尽管最后设置的cookie会被保留。所以在Firefox里不要超过cookie数的限制。</li>
</ul>
<p>cookie的总大小在各浏览器中也是不同的：</p>
<ul>
<li>Firefox和Safari允许cookie多达4097个字节，其中4096个字节是名字和值，1个字节是=号。</li>
<li>Opera允许cookie多达4096个字节，这些字节包含名字、值和=号。</li>
<li>IE允许4095个字节，这些字节包含名字、值和=号。</li>
</ul>
<p>注意这里用的字节，也就是，如果是多字节字符，自然就会占用两个字节。在所有浏览器里，如果设置的cookie大小超过限制，那么它就会被忽略或者不被设置。</p>
<p>从上面，我们可以看到，Cookie确实存在一些不足，但是它的一些缺点也正是它的优点，例如每个请求都会被放到包头里发送给服务器，正是这个特性我们才能很方便的传输sessionid。Cookie的出现可谓大大推动了网页的发展，而且在未来很长的一段时间里，Cookie还会继续发挥它的作用。但是也正是由于Cookie存在种种的不足，才会有新的本地存储技术出现的需求。</p>
<h2 style="border-bottom:2px solid #487858;">userData</h2>
<p>userData是微软在第一次浏览器大战中的产物，属于DHTML中的一种技术。相比起Cookie，userData在每个域名下可存储达的数据提升了不少，但是具体的大小视domain的安全域而定：</p>
<table class="mytable" cellspacing="0" style="width:100%;">
<tr>
<th scope="row" class="left top">Security Zone</th>
<th scope="col">Document Limit (KB)</th>
<th scope="col">Domain Limit (KB)</th>
</tr>
<tr>
<td class="spec">Local Machine</td>
<td>128</td>
<td>1024</td>
</tr>
<tr>
<td class="spec">Intranet</td>
<td>512</td>
<td>10240</td>
</tr>
<tr>
<td class="spec">Trusted Sites</td>
<td>128</td>
<td>1024</td>
</tr>
<tr>
<td class="spec">Internet</td>
<td>128</td>
<td>1024</td>
</tr>
<tr>
<td class="spec">Restricted</td>
<td>64</td>
<td>640</td>
</tr>
</table>
<p></p>
<p>总的来说，考虑到各种情况，最安全的做法是把文件控制在64K以下。文件的数据是XML格式，保存在系统盘的某个目录下，例如在XP下（假设C为系统盘），保存的位置为C:\Documents and Settings\用户名\UserData或C:\Documents and Settings\用户名\Application Data\Microsoft\Internet Explorer\UserData下，而在VISTA下则保存在C:\Users\用户名\AppData\Roaming\Microsoft\Internet Explorer\UserData下。</p>
<p>userData支持IE5以上的浏览器，要使用userData，必须以一个HTML元素作为宿主。也就是说，userData并不能单独使用，而必须依附于某个HTML标签上，并设置behavior：</p>
<table class="mytable" cellspacing="0" width="100%">
<tr>
<th scope="row" class="specalt top">XML</th>
<td class="top">&lt;Prefix: CustomTag ID=sID STYLE="<a href="http://msdn.microsoft.com/en-us/library/ms533503.aspx" target="_blank">behavior</a>:url('#default#userData')" /&gt; </td>
</tr>
<tr>
<th scope="row" class="specalt">HTML</th>
<td>&lt;ELEMENT STYLE="behavior:url('#default#userData')" ID=sID&gt;</td>
</tr>
<tr>
<th scope="row" rowspan="2" class="specalt">Scripting</th>
<td>object.<a href="http://msdn.microsoft.com/en-us/library/ms537842.aspx" target="_blank">style</a>.behavior = "url('#default#userData')"</td>
</tr>
<tr>
<td>object.<a href="http://msdn.microsoft.com/en-us/library/ms535922.aspx" target="_blank">addBehavior</a>("#default#userData")</td>
</tr>
</table>
<p></p>
<p>但需要注意的是并不是所有的HTML标签都能用于userData，例如绑定userData到html、head、title或者style上进行存储时就会发生错误。</p>
<p>userData的数据会一直存在，直到被删除或者到过期时间。并且基于安全的考虑，一个 userData 存储区只能用于同一目录和对同一协议进行存储。</p>
<p>跟cookie一样，userData的数据也是没有进行加密的，所以也不建议把一些重要信息保存在里面。</p>
<p>userData在数据的本地储存来说，比cookie进步了不少，但是它有个致命的缺点：仅支持IE。仅凭这一点，就注定了userData并不会有太大的作为，只能用作配合其他本地存储技术兼容低版本的IE。</p>
<p>userData具体的使用方法这里就不深入了，详情可以参阅微软的<a href="http://msdn.microsoft.com/en-us/library/ms531424.aspx" target="_blank">文档</a>。</p>
<h2 style="border-bottom:2px solid #487858;">Local Shared Object</h2>
<p>2002年，Adobe在Flash6中引入了一个新功能，并很不幸的获取了一个及具有迷惑性的名字：“Flash Cookies”。 但在Flash中，这个功能被称作Local Shared Objects或者LSO更为合适。 简单来说，这个技术允许Flash对象在每个域名上存储100KB的数据。</p>
<p>2005年，Brad Neuberg开发了一个早期的Flash到JavaScript的桥接原型接口，叫AJAX Massive Storage System(AMASS)，但是受到Flash技术的种种局限。 </p>
<p>2006年，随着Flash 8的ExternalInterface的引入，在Javascript中访问LSO变得更加简单和快速。Brad重写了AMASS并把它整合到流行的Dojo工具框架中，取名dojox.storage。同时这个技术也允许每个域名下存储100K的数据，而超过这个限制则要增加到下一个大小等级（1MB，10MB等），就会弹出提示让用户确认。</p>
<p>Adobe Flash Player不允许第三方的LSO进行跨域分享。例如，一个www.example.com的LSO不能被www.example2.com读取。然而第一方的网站可以通过专门的XML文件上的设置传送数据给第三方。</p>
<p>用户可以通过在Adobe网站上的全局存储设置面板禁用LSO，也可以右击Flash Player，在设置里对每个网站的设置进行调整。后面的方法也允许用户删除LSO。当然用户也可以手动或者借助第三方的软件来删除LSO。</p>
<p>直到版本10.1，Adobe Flash Player才支持浏览器的<a href="http://en.wikipedia.org/wiki/Privacy_mode" target="_blank">私隐模式</a>，支持的浏览器包括Chrome，Firefox，IE和Safari。</p>
<p>LSO解决了上面提到的Cookie的一些问题，例如大小，安全等。跟Cookie不同，LSO被保存为二进制文件（不过变量名具有可读性）。作为本地存储的替代方案，LSO具有了不少优点，但是缺点也是明显，就是它需要安装Flash这个插件。虽然现在Flash的普及率很高，但是这种依赖插件的技术始终不能解决问题的根源，而且为了使用这个方案，我们不得不引入额外的swf和js文件。</p>
<p>P.S. 本人极不喜欢Flash……</p>
<h2 style="border-bottom:2px solid #487858;">Gears</h2>
<p>2007年，Google启动了Gears项目。Gears是一个开源的浏览器插件，目标是为浏览器提供额外的兼容功能（例如为IE提供geolocation的API）。Gears提供了一个API用来嵌入一个基于SQLite的SQL数据库。在得到用户的允许后，Gears可以在数据库存储没有大小限制的数据。</p>
<p>Gears主要有以下特点：</p>
<table>
<tbody>
<tr>
<td class="feature"><img width="43px" height="35px" alt="Desktop" src="http://gears.google.com/images/localserver.gif"></td>
<td class="feature">让网页应用跟桌面进行更自然的交互。</td>
</tr>
<tr>
<td class="feature">
<img width="43px" height="35px" alt="Desktop" src="http://gears.google.com/images/database.gif">
</td>
<td class="feature">具有完整搜索功能数据库的本地存储。</td>
</tr>
<tr>
<td class="feature">
<img width="43px" height="35px" alt="Desktop" src="http://gears.google.com/images/workerpool.gif">
</td>
<td class="feature">在后台运行Javascript以提供性能。</td>
</tr>
</tbody>
</table>
<p></p>
<p>所以，本地存储事实上只是Gears的其中一个功能。一个嵌入式数据库对本地存储的需求来说是绰绰有余了，然而跟Flash一样，Gears只不过是一个插件，还是一个跟Flash的普及率不可相提并论的插件。而且作为一个插件，它对浏览器的支持也是不足够的，我们看看它的浏览器支持情况：</p>
<ul>
<li>Firefox 1.5-3.6</li>
<li>Internet Explorer 6.0-8.0</li>
</ul>
<p>当然造成这个状况也有有原因，就是因为Gears项目已经停止更新了。</p>
<p>2010年，Google官方宣布逐渐放弃对Gears的支持，并把工作重点转移在HTML5上……</p>
<h2 style="border-bottom:2px solid #487858;">globalStorage</h2>
<p>这是一个全局对象(globalStorage)，它维护着各种公共的或者私有的，可以用来长时期保存数据的存储空间（例如，在多重的页面和浏览器会话之间）。目前只有Firefox 2+支持。</p>
<p>globalStorage不是一个Storage实例，而是一个包含StorageObsolete实例的StorageList实例。举个栗子：</p>
<ul>
<li>globalStorage['developer.mozilla.org'] - 在developer.mozilla.org下面所有的子域都可以通过这个存储对象来进行读和写。</li>
<li>globalStorage['mozilla.org'] - 在mozilla.org域名下面的所有网页都可以通过这个存储对象来进行读和写。</li>
<li>globalStorage['org'] - 在.org域名下面的所有网页都可以通过这个存储对象来进行读和写。</li>
<li>globalStorage[] - 在任何域名下的任何网页都可以通过这个存储对象来进行读和写。</li>
</ul>
<p>Firefox 2允许访问比当前文档域名层次高的存储的对象，例如在developer.mozilla.org上你可以访问mozilla.org或者org的存储对象。但是由于安全的因素在Firefox 3以上版本中则不再允许，你只能访问当前域名的存储对象。 </p>
<h2 style="border-bottom:2px solid #487858;">Web Storage</h2>
<p>鉴于以上本地存储的方案的各种不给力，一种更强力的本地存储技术的出现貌似是很顺应潮流的事情。<a href="http://dev.w3.org/html5/webstorage/" target="_blank">Web Storage</a>被设计出来的目的就是为了提供更大，更安全，更容易使用的存储方式。</p>
<p>需要注意的一点：我们说到Web Storage总是认为它是HTML5的东西，事实上，它并不是HTML5规范的一部分，它有自己独立的一份<a href="http://dev.w3.org/html5/webstorage/" target="_blank">规范</a>。</p>
<p>Web Storage可分为local storage和session storage。local storage提供了5MB的存储空间，而sessiong storage甚至没有限制。我们来看看local storage能为我们做什么：</p>
<ul>
<li>减少网络流量</li>
<li>明显的加快显示速度</li>
<li>RPC调用时缓存数据</li>
<li>在启动时加载缓存数据（加快启动速度）</li>
<li>保存临时状态</li>
<li>应用切换时状态的恢复</li>
<li>防止网络断开时的工作的丢失</li>
</ul>
<p>local storage跟session storage除了在存储空间上不一样外，在生命周期及存储事件等特性上的表现也不一样：</p>
<table class="mytable" cellspacing="0" style="width:100%;">
<tr>
<th scope="row" class="left top">Storage<br />类型</th>
<th scope="col" width="200">大小限制</th>
<th scope="col">生命周期</th>
<th scope="col">对其他窗口和Tab的可用性</th>
<th scope="col">支持的数据类型</th>
</tr>
<tr>
<td class="spec">LocalStorage</td>
<td>每个浏览器的每个应用限制5MB。根据<a href="http://www.w3.org/TR/2009/WD-webstorage-20091222/#the-storage-interface" target="_blank">Web Storage规范</a>，用户可以根据需要增加这个限制，但是只有少数的浏览器支持。</td>
<td>保存在硬盘上直到被用户或者应用删除。</td>
<td>共享于运行同一个web应用的浏览器的所有窗口跟标签页。</td>
<td>只支持键-值对的字符串。</td>
</tr>
<tr>
<td class="spec">SessionStorage</td>
<td>只受到系统内存的限制</td>
<td>只存活于使用它的窗口或标签页</td>
<td>只能由创建它的窗口或者标签页访问</td>
<td>只支持键-值对的字符串。</td>
</tr>
</table>
<p></p>
<p>可以看出，session storage只是会话级的存储，只有同一个会话的窗口或者标签页能访问它，并且会话结束数据也会同时销毁。而local storage是一种持久化的存储，就算关闭浏览器数据依然存在，并且能在多个窗口和标签页间共享数据。</p>
<p>下面我们来看看local storage是怎样在浏览器中共享数据的：</p>
<p>每个浏览器上的每个web应用都有自己的一个LocalStorage，大小限制为5MB，并能在所有的这个浏览器上的窗口和标签页间共享。例如，假设你在Chrome上运行MyWebApp，如果你在多个标签页和窗口上运行MyWebApp，它们共享相同的大小限制为5MB的LocalStorage数据。如果你在其他浏览器打开相同的应用，例如Firefox，那么这个新的浏览器将会有它自己的LocalStorage供它上面的窗口和标签页进行共享。请看下面图片：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/09/localStorage_shared.png" alt="" title="" width="622" height="439" class="alignnone size-full wp-image-1176" /></p>
<p>LocalStorage只能存储键值对字符串类型的数据，如果你想保存其他类型的数据，你只能在保存的时候把数据转化为字符串，然后在读取的时候再转化回去。就像cookie一样，LocalStorage和SessionStorage可以被诸如Chrome上的Developer Tools，Sarafi上的Web Inspector等工具检测得到。通过这些工具，用户可以移除保存的数据或者查看访问的网页保存了哪些数据。</p>
<p>LocalStorage并不是安全的存储，它保存的数据并不会进行加密，所有不宜保存一些敏感的数据。</p>
<p>Web Storage还有个重要特性就是Storage事件。当LocalStorage或者SessionStorage的数据被保存，修改或者删除的时候，当前窗口或者标签页的Storage事件就会触发。触发的Storage事件对象包含了事件发生时的storage对象，URL以及变化前后的键值。需要注意的时候，虽然规范里要求Storage事件在同一个浏览器里能被所有的窗口或者标签页触发，但是只有少数的浏览器实现了这个功能。另外，如果storage被清空了，那么事件不会包含任何被删键值对的信息。</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/localstorage_event.jpg" alt="" title="localstorage_event" width="550" height="266" class="alignnone size-full wp-image-1209" /></p>
<p>StorageEvent对象包含了以下的方法：</p>
<table class="mytable" cellspacing="0" style="width:100%;">
<tr>
<th scope="row" class="left top">方法</th>
<th scope="col">描述</th>
</tr>
<tr>
<td class="spec">getKey</td>
<td>返回被修改的键。</td>
</tr>
<tr>
<td class="spec">getNewValue</td>
<td>返回被修改后的键值，如果值没有修改或者调用的是Storage.clear()的话，则返回null。</td>
</tr>
<tr>
<td class="spec">getOldValue</td>
<td>返回被修改前的键值，如果值没有修改或者调用的是Storage.clear()的话，则返回null。</td>
</tr>
<tr>
<td class="spec">getStorageArea</td>
<td>返回触发事件的SessionStorage或者LocalStorage对象。</td>
</tr>
<tr>
<td class="spec">getURL</td>
<td>触发当前存储事件的页面的url。</td>
</tr>
</table>
<p></p>
<p>目前支持Web Storage的浏览器如下：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/10/localstorage_support.jpg" alt="" title="localstorage_support" width="640" height="55" class="alignnone size-full wp-image-1208" /></p>
<h2 style="border-bottom:2px solid #487858;">总结</h2>
<p>可以看到，本地存储一直在发展着，特别是HTML5阶段的Web Storage，已经是一个比较完善的实现。但是，HTML5的普及还遥遥无期，只有比较新的浏览器才支持Web Storage，而有的就算支持Web Storage也不完全支持Storage事件，应此，为了兼容大部分的浏览器，就必须协同其他的本地存储技术一起使用，例如对不支持Web Storage的浏览器，譬如IE6和IE7则使用userData，低版本的Firefox则使用globalStorage。目前已经有不少的js库去实现这个功能，例如：<a href="http://codecto.com/2011/07/localstorage-cross-browser-store-js/" target="_blank">store.js</a>， <a href="https://github.com/hugeinc/USTORE.js" target="_blank">USTORE.js</a>， <a href="https://github.com/kbjr/Box.js/blob/master/box.js" target="_blank">Box.js</a>等等。</p>
<p>参考资料：<br />
<a href="http://hc.apache.org/httpclient-3.x/cookies.html" target="_blank">http://hc.apache.org/httpclient-3.x/cookies.html</a><br />
<a href="http://www.nczonline.net/blog/2008/05/17/browser-cookie-restrictions/" target="_blank">http://www.nczonline.net/blog/2008/05/17/browser-cookie-restrictions/</a><br />
<a href="http://msdn.microsoft.com/en-us/library/ms531424.aspx" target="_blank">http://msdn.microsoft.com/en-us/library/ms531424.aspx</a><br />
<a href="http://shop.oreilly.com/product/9780596806033.do" target="_blank">HTML5: Up and Running</a><br />
<a href="http://en.wikipedia.org/wiki/Local_Shared_Object" target="_blank">http://en.wikipedia.org/wiki/Local_Shared_Object</a><br />
<a href="http://gears.google.com/" target="_blank">http://gears.google.com/</a><br />
<a href="https://developer.mozilla.org/cn/DOM/Storage" target="_blank">https://developer.mozilla.org/cn/DOM/Storage</a><br />
<a href="http://code.google.com/intl/zh-CN/webtoolkit/doc/latest/DevGuideHtml5Storage.html" target="_blank">http://code.google.com/intl/zh-CN/webtoolkit/doc/latest/DevGuideHtml5Storage.html</a><br />
<a href="http://dev.opera.com/articles/view/web-storage/" target="_blank">http://dev.opera.com/articles/view/web-storage/</a><br />
<a href="https://developer.mozilla.org/en/DOM/Storage" target="_blank">https://developer.mozilla.org/en/DOM/Storage</a><br />
<a href="http://sixrevisions.com/html/introduction-web-storage/" target="_blank">http://sixrevisions.com/html/introduction-web-storage/</a>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/10/local-storage-brief/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Unbuntu下配置无线网络</title>
		<link>http://www.icyfire.me/2011/10/unbuntu-wifi-wpa-setting/</link>
		<comments>http://www.icyfire.me/2011/10/unbuntu-wifi-wpa-setting/#comments</comments>
		<pubDate>Mon, 17 Oct 2011 14:00:15 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[Unbuntu]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1206</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/10/ubuntu.jpg" alt="" title="ubuntu" width="640" height="64" class="alignnone size-full wp-image-1207" />

最近在一台不用的笔记上装了Ubuntu 11.10的server版，折腾半天终于让它连上了屋里的WIFI，发现跟网上的方法还是有一点点的区别的。遂作下笔记。 <a href="http://www.icyfire.me/2011/10/unbuntu-wifi-wpa-setting/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>最近在一台不用的笔记上装了Ubuntu 11.10的server版，折腾半天终于让它连上了屋里的WIFI，发现跟网上的方法还是有一点点的区别的。遂作下笔记，只记下过程，命令不作深入解释。</p>
<p>另外WIFI使用的安全类型是WPA2，所以此方法只适用于WPA安全类型的WIFI。</p>
<h2 style="border-bottom:2px solid #FF7D27;">WPA类型的WIFI设置</h2>
<p>首先，先看看一些网络的初始状态：</p>
<pre class="brush: shell; gutter: false"># iwconfig wlan0
wlan0     IEEE 802.11abgn  ESSID:off/any
          Mode:Managed  Access Point: Not-Associated   Tx-Power=0 dBm
          Retry  long limit:7   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:off

# ifconfig wlan0
wlan0     Link encap:Ethernet  HWaddr 00:21:6b:66:5c:24
          inet6 addr: fe80::221:6bff:fe66:5c24/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:18 errors:0 dropped:0 overruns:0 frame:0
          TX packets:16 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:5514 (5.5 KB)  TX bytes:1752 (1.7 KB)</pre>
<p>OK，正式开始。</p>
<p><strong>首先，我们用wpa_passphrase命令生成一个网络配置文件</strong>：</p>
<pre class="brush: shell; gutter: false"># wpa_passphrase ESSID KEY >> /etc/wpa_supplicant.conf</pre>
<p>ESSID就是WIFI的essid，KEY就是WIFI的密码。此命令将会在/etc下生成一个名为wpa_supplicant.conf的文件。</p>
<p><strong>然后，我们运行以下两个命令启动无线网卡。</strong></p>
<pre class="brush: shell; gutter: false"># iwconfig wlan0 power on
# ifconfig wlan0 up</pre>
<p>再次运行iwconfig wlan0，我们可以发现wlan0的状态已经发生改变：</p>
<pre class="brush: shell; gutter: false"># iwconfig wlan0
wlan0     IEEE 802.11abgn  ESSID:off/any
          Mode:Managed  Access Point: Not-Associated   Tx-Power=15 dBm
          Retry  long limit:7   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:on</pre>
<p><strong>运行以下命令连上WIFI：</strong></p>
<pre class="brush: shell; gutter: false"># wpa_supplicant -B -Dwext -iwlan0 -c/etc/wpa_supplicant.conf</pre>
<p>再次运行iwconfig wlan0命令，发现wlan0的状态再次发生变化：</p>
<pre class="brush: shell; gutter: false"># iwconfig wlan0
wlan0     IEEE 802.11abgn  ESSID:"MY_ESSID"
          Mode:Managed  Frequency:2.447 GHz  Access Point: 34:08:04:0B:73:4E
          Bit Rate=1 Mb/s   Tx-Power=15 dBm
          Retry  long limit:7   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:on
          Link Quality=70/70  Signal level=-14 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:5   Missed beacon:0</pre>
<p><strong>最后运行一下命令获取IP：</strong></p>
<pre class="brush: shell; gutter: false"># dhclient wlan0</pre>
<p>验证一下是否成功：</p>
<pre class="brush: shell; gutter: false"># ifconfig wlan0
wlan0     Link encap:Ethernet  HWaddr 00:21:6b:66:5c:24
          inet addr:192.168.0.107  Bcast:192.168.0.255  Mask:255.255.255.0
          inet6 addr: fe80::221:6bff:fe66:5c24/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:108 errors:0 dropped:0 overruns:0 frame:0
          TX packets:25 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:29588 (29.5 KB)  TX bytes:2990 (2.9 KB)</pre>
<p>搞定。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/10/unbuntu-wifi-wpa-setting/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>编写第一个Django应用：第一部分</title>
		<link>http://www.icyfire.me/2011/10/writing_your_first_django_app/</link>
		<comments>http://www.icyfire.me/2011/10/writing_your_first_django_app/#comments</comments>
		<pubDate>Mon, 10 Oct 2011 12:01:32 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[程序语言]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1131</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/08/django.jpg" alt="Django" />

对Django这个框架感兴趣好久了，却一直懒于研究学习，现在跟随官网的教程跑一遍，学学Django同时也继续学学Python。

<pre class="brush: bash; gutter: true">wget http://media.djangoproject.com/releases/1.3/Django-1.3.tar.gz
tar xzvf Django-1.3.tar.gz
cd Django-1.3
python setup.py install</pre> <a href="http://www.icyfire.me/2011/10/writing_your_first_django_app/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<style type="text/css">
#content p {margin-bottom:10px;}
#content .djg-c { border: 1px solid #94DA3A; margin: 1em 0; padding: 0.8em 1em 0.8em 65px;padding-left: 65px;background: url("http://www.icyfire.me/wp-content/uploads/2011/08/docicons-note.gif") no-repeat scroll 0.8em 0.8em transparent;}
#content .djg-c-t {color: #487858;font-weight: bold;margin:0;}
#content .djg-c-c {margin: 10px 0 0;}
#content .djg-title {color:#487858;}
#content .djg-pre {color:#234F32;}
#content .djg-link {color:#AB5603;text-decoration:none;}
#content .djg-link2 {color:#234F32;text-decoration:none;border-bottom: 1px #234F32 dotted;}
</style>
<p>对Django这个框架感兴趣好久了，却一直懒于研究学习，现在跟随官网的教程跑一遍，学学Django同时也继续学学Python。</p>
<p>在开始之前，我们先把Python和Django这个框架安装好。</p>
<p>官网：https://www.djangoproject.com/<br />
下载：http://www.djangoproject.com/download/1.3/tarball/</p>
<p>由于兼容性问题，Django并不支持Python3+，只支持版本2.4到2.7，所以如果你的系统（本人使用的是SUSE Linux Enterprise Server 10 SP3，以下都是以这个系统为例）没有安装Python，或者版本不符合，那么就先要安装一个合适的版本，我使用的是2.7.2版本:</p>
<pre class="brush: bash; gutter: true">wget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tar.bz2
tar jxvf Python-2.7.2.tar.bz2
cd Python-2.7.2
./configure
make &amp;&amp; make install</pre>
<p>下载完后运行python setup.py install进行安装：</p>
<pre class="brush: bash; gutter: true">wget http://media.djangoproject.com/releases/1.3/Django-1.3.tar.gz
tar xzvf Django-1.3.tar.gz
cd Django-1.3
python setup.py install</pre>
<p>安装完成后，验证下是否成功，输入python进入命令行，然后输入如下语句导入django并且输出django的版本号：</p>
<pre class="brush: python; gutter: true">&gt;&gt;&gt; import django
&gt;&gt;&gt; print django.get_version()
1.3</pre>
<p>OK，下面我们可以正式开始了。</p>
<p>在这个教程里，我们会创建一个简单的投票应用。它包括两部分：</p>
<ul>
<li>一个公共网页让人查看投票和进行投票。</li>
<li>一个管理网页让你添加，修改和删除投票</li>
</ul>
<div class="djg-c">
<p class="djg-c-t">获取帮助：</p>
<p class="djg-c-c">如果你对这个教程有任何疑问，请到django-users留言或者在irc.freenode.net上访问#django，找一些可能为你提供帮助的Django用户。</p>
</div>
<h2 class="djg-title">创建项目</h2>
<p>下面开始建立一个项目。首先<span class="pre">cd</span>到存放项目的目录，然后运行<span class="djg-pre">django-admin.py startproject mysite</span>命令创建项目：</p>
<pre class="brush: bash; gutter: true">cd /home/python
django-admin.py startproject mysite</pre>
<p>执行完后将会在当前目录创建一个名为mysite的项目。</p>
<div class="djg-c">
<p class="djg-c-t">发布包的文件名可能不同</p>
<p class="djg-c-c">如果你是通过Linux发布包管理系统（例如apt-get或者yum）安装Django，<span class="djg-pre">django-admin.py</span>可能会被重命名为django-admin，你可以忽略命令里的<span class="djg-pre">.py</span>继续完成这个文档。</p>
</div>
<div class="djg-c">
<p class="djg-c-t">Mac OS X权限问题</p>
<p class="djg-c-c">如果你使用的是Mac OS X，当运行django-admin.py startproject的时候可能会看到信息"permission denied"。这是因为像OS X这种基于Unix的系统，一个文件在运行前必须是"executable"的。你可以打开Terminal.app并且去到django-admin.py被安装到的目录，并运行命令<span class="djg-pre">chmod +x django-admin.py</span>来解决这个问题。</p>
</div>
<div class="djg-c">
<p class="djg-c-t">注意</p>
<p class="djg-c-c">要避免跟Python和Django组件的命名冲突，避免使用像django和test这样的命名。</p>
</div>
<p>如果你是通过<span class="djg-pre">python setup.py</span>安装Django的话，<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/" target="_blank" class="djg-link">django-admin.py</a>应该会在你的系统路径下。如果它不在这个路径上的话，你可以在<span class="djg-pre">site-packages/django/bin</span>里找到它，<span class="djg-pre">site-packages</span>是你安装Python的目录。你可以考虑把某个路径软链接到<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/" target="_blank" class="djg-link">django-admin.py</a>，例如<span class="djg-pre">/usr/local/bin</span>。</p>
<div class="djg-c">
<p class="djg-c-t">代码应该放在哪</p>
<p class="djg-c-c">如果你是PHP出身的话，你大概会把代码放在Web Server的根目录下（例如<span class="djg-pre">/var/www</span>）。但在Django里，别这么干。把Python代码放在Web Server的根目录里并不是一个好主意，因为有被人在网页上看到代码的风险。这个不利于安全。</p>
<p class="djg-c-c">把你的代码放在根目录外面的地方，例如<span class="djg-pre">/home/mycod</span>（我这里用的目录是<span class="djg-pre">/home/python</span>）。</p>
</div>
<p>回到正题，<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-startproject" target="_blank" class="djg-link2">startproject</a>创建的目录结构如下：</p>
<pre class="brush: text; gutter: false">mysite/
    __init__.py
    manage.py
    settings.py
    urls.py</pre>
<p>这些文件的用途是：</p>
<ul>
<li><span class="djg-pre">__init__.py</span>: 一个空文件让Python知道这个目录是一个Python包。(如果你是一个Python初学者，可以看官方的Python文档了解<a href="http://docs.python.org/tutorial/modules.html#packages" target="_blank" class="djg-link">更多关于包</a>的内容)</li>
<li><span class="djg-pre">manage.py</span>: 一个命令行工具用于跟项目的各种交互。详情查看<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/" target="_blank" class="djg-link">django-admin.py和manage.py</a>这两个文件。</li>
<li><span class="djg-pre">settings.py</span>: 当前项目的设置和配置。详情可以查看<a href="https://docs.djangoproject.com/en/dev/topics/settings/" target="_blank" class="djg-link">Django settings</a>。</li>
<li><span class="djg-pre">urls.py</span>: 当前项目的URL定义。详情可以查看<a href="https://docs.djangoproject.com/en/dev/topics/http/urls/" target="_blank" class="djg-link">URL dispatcher</a>。</li>
</ul>
<h3>开发用服务器</h3>
<p>下面启动Django development server来检查下成果。进入项目目录<span class="djg-pre">mysite</span>运行命令<span class="djg-pre">python manage.py runserver</span>：</p>
<pre class="brush: bash; gutter: false">Validating models...

0 errors found
Django version 1.3, using settings 'mysite.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.</pre>
<p>你已经启动了Django的开发服务器，一个用纯Python编写的轻量级Web server。我们已经把它包含在Django里，这样你就可以进行快速的开发而不需要去配置诸如apache这样的Production服务器，除非你已经准备好要发布了。</p>
<p>注意不要把这个服务器当作发布环境用，它是专门在开发环境中使用的。</p>
<p>现在服务已经启动了，你可以在浏览器上访问<a href="http://127.0.0.1:8000/" target="_blank">http://127.0.0.1:8000/</a>。你将会看到欢迎页面，如下图：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/2011-8-4-20-22-32-640x167.png" alt="" title="It worked!" width="640" height="167" class="aligncenter size-medium wp-image-1133" /></p>
<div class="djg-c">
<p class="djg-c-t">改变端口</p>
<p class="djg-c-c"><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-runserver" target="_blank" class="djg-link2">runserver</a>命令的默认端口是8000。</p>
<p class="djg-c-c">如果你想改变端口，你可以把它作为参数传给命令行，例如，这个命令会在端口8080上启动服务：</p>
<pre class="brush: bash; gutter: false">python manage.py runserver 8080</pre>
</p>
<p class="djg-c-c">如果想改变IP，那么可以跟端口一起传给命令行。如果要监听所有的公共IP，可以这样：</p>
<pre class="brush: bash; gutter: false">python manage.py runserver 0.0.0.0:8000</pre>
</p>
<p class="djg-c-c">开发服务器的文档可以在<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-runserver" target="_blank" class="djg-link2">runserver</a>里找到。</p>
</div>
<h3>数据库配置</h3>
<p>现在，编辑<span class="djg-pre">settings.py</span>文件。修改<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASES" target="_blank" class="djg-link2">DATABASES</a>的<span class="djg-pre">default</span>项里下面的值：</p>
<ul>
<li><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASE-ENGINE" target="_blank" class="djg-link2">ENGINE</a>：django.db.backends.postgresql_psycopg2或者django.db.backends.mysql和django.db.backends.sqlite3。也支持<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-DATABASE-ENGINE" target="_blank" class="djg-link2">其他数据库</a>。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-NAME" target="_blank" class="djg-link2">NAME</a>：数据库名。如果使用的是SQLite的话，数据库则是系统里的一个文件，所以<span class="django-literal">NAME</span>应该设为包含这个文件名字的绝对路径（注意路径要使用斜杠/，无论在什么系统上）。这个文件不需要创建，在第一次进行数据库同步的时候会自动创建这个文件。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-USER" target="_blank" class="djg-link2">USER</a>：用户名。使用SQLite的话则不需要。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-PASSWORD" target="_blank" class="djg-link2">PASSWORD</a>：密码。使用SQLite的话则不需要。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-HOST" target="_blank" class="djg-link2">HOST</a>：主机地址。使用SQLite的话则不需要。如果数据库安装在同一台机器上，也可以不指定。</li>
</ul>
<p>如果你对数据库不太熟悉的话，建议你使用SQLite（把<span class="djg-pre">ENGINE</span>设为<span class="djg-pre">django.db.backends.sqlite3</span>）。SQLite已经作为Python 2.5以上版本的一部分包含在里面，所以你不需要作任何的安装。</p>
<p>在编辑<span class="djg-pre">settings.py</span>的时候，你会注意到文件底部的<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>配置项。这些变量保存了那些随着Django实例启动的Django应用(以下用app表示)的名字。app可以被使用到多个项目里，你也可以把它们打包和发布出去供其他人使用。</p>
<p><a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>默认包含了以下app，这些app全部由Django自带：</p>
<ul>
<li><a href="https://docs.djangoproject.com/en/dev/topics/auth/#module-django.contrib.auth" target="_blank" class="djg-link2">django.contrib.auth</a> -- 一个认证系统。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#module-django.contrib.contenttypes" target="_blank" class="djg-link2">django.contrib.contenttypes</a> -- 内容类型框架。</li>
<li><a href="https://docs.djangoproject.com/en/dev/topics/http/sessions/#module-django.contrib.sessions" target="_blank" class="djg-link2">django.contrib.sessions</a> -- session框架。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/contrib/sites/#module-django.contrib.sites" target="_blank" class="djg-link2">django.contrib.sites</a> -- 多网站管理框架。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/contrib/messages/#module-django.contrib.messages" target="_blank" class="djg-link2">django.contrib.messages</a> -- 消息管理框架。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#module-django.contrib.staticfiles" target="_blank" class="djg-link2">django.contrib.staticfiles</a> -- 静态文件管理框架。</li>
</ul>
<p>为了方便使用，这些通常网站都会用到的应用已经默认包含在项目里。</p>
<p>这些app每个都至少要用到一个以上的数据库表，所以在使用这些应用前我们先要创建这些表。运行一下命令：</p>
<pre class="brush: bash; gutter: false">python manage.py syncdb</pre>
<p><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-syncdb" target="_blank" class="djg-link2">syncdb</a>命令会根据<span class="djg-pre">settings.py</span>文件里的<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>设置创建必需的数据表。你会看到创建表的信息以及会提示你为认证系统创建超级用户，跟着提示做就行了。</p>
<div class="djg-c">
<p class="djg-c-t">简化</p>
<p class="djg-c-c">就像我们上面说的，基于常理，默认的那些应用会被自动包含，但是并不是所有人都会用到它们。所以如果你不需要用到这些应用，你可以在执行<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-syncdb" target="_blank" class="djg-link2">syncdb</a>命令前随意把它们在<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>里注释掉或者删掉。<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-syncdb" target="_blank" class="djg-link2">syncdb</a>只会为<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>里的应用创建表。</p>
</div>
<h2 class="djg-title">创建模型</h2>
<p>现在环境已经搭好了，可以正式开工了。</p>
<p>在Django里，你写的每个应用都包含一个Python的包，这个包会根据一个特定的约定放在<a href="http://docs.python.org/tutorial/modules.html#the-module-search-path" target="_blank" class="djg-link">Python path</a>里。Django会自动为每个app创建一些基本的目录结构，所以你可以专心写代码而不必在创建目录上费心。</p>
<div class="djg-c">
<p class="djg-c-t">Projects vs. apps</p>
<p class="djg-c-c">项目跟app有什么不同呢？app是一个实现某个功能的网页应用，例如一个网页日志系统，一个公共数据数据库或者一个简单的投票应用。而一个项目则是由一堆配置和应用组成的网页。一个项目可以包含多个应用，而一个应用可以使用在多个项目里。</p>
</div>
<p>你的应用可以存放在<a href="http://docs.python.org/tutorial/modules.html#the-module-search-path" target="_blank" class="djg-link">Python path</a>上的任何地方。简单起见，我们在<span class="djg-pre">mysite</span>目录里创建一个投票应用。确认你的当前目录为<span class="djg-pre">mysite</span>，并输入命令：</p>
<pre class="brush: bash; gutter: false">python manage.py startapp polls</pre>
<p>这样会创建一个目录polls，如下：</p>
<pre class="brush: text; gutter: false">polls/
    __init__.py
    models.py
    tests.py
    views.py</pre>
<p>这个目录结构将会存放这个poll应用。</p>
<p>在Django里写一个有数据库操作的网页应用第一步要做的就是定义模型（Model），模型实质上就是附带元数据的数据层(database layout)。</p>
<div class="djg-c">
<p class="djg-c-t">原理（Philosophy）</p>
<p class="djg-c-c">模型是跟你的数据相关的一个单一和明确的数据源。它包含了你所储存的数据的一些必要字段和行为。Django遵循<a href="https://docs.djangoproject.com/en/dev/misc/design-philosophies/#dry" target="_blank" class="djg-link">DRY原则</a>，目的是在一个地方定义你的数据模型然后根据它自动派生出其他的东西。</p>
</div>
<p>在我们这个简单的投票应用里，我们创建了两个模型：polls和choices。一个poll包含有一个问题和一个发表日期。一个choice有两个字段：选项文字和投票计数。每一个choice都跟一个poll关联。</p>
<p>这个概念将由一个简单的Python类来实现。编辑文件<span class="djg-pre">polls/models.py</span>：</p>
<pre class="brush: python; gutter: true">from django.db import models

class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(max_length=200)
    votes = models.IntegerField()</pre>
<p>代码很直白。每个模型都由一个<a href="https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model" target="_blank" class="djg-link2">django.db.models.Model</a>的子类实现。每个模型包含一些变量，每个变量对应一个数据库里的字段。</p>
<p>每个字段由类<a href="https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field" target="_blank" class="djg-link2">Field</a>的一个实例表示，例如字符类型的字段由<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.CharField" target="_blank" class="djg-link2">CharField</a>实现，时间类型的字段则由<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateTimeField" target="_blank" class="djg-link2">DateTimeField</a>实现，这样Django就知道每个字段保存的是什么类型的数据了。</p>
<p>每个<a href="https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field" target="_blank" class="djg-link2">Field</a>实例的名字（例如<span class="djg-pre">question</span>和<span class="djg-pre">pub_date</span>）都是机器友好格式的字段名。这些字段会用在你的Python代码里，而数据库则会把它用作字段名。</p>
<p>你可以在<a href="https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field" target="_blank" class="djg-link2">Field</a>的第一个参数指定一个可读的名字，这个名字会用在Django内部多个地方。如果没有指定这个名字，Django会使用机器可读的名字。在这个例子里，我们只为<span class="djg-pre">Poll.pub_date</span>定义了人可读的名字，模型里其他所有的字段，机器可读名字已经可作为人可读名字。</p>
<p>一些<a href="https://docs.djangoproject.com/en/dev/howto/custom-model-fields/#django.db.models.Field" target="_blank" class="djg-link2">Field</a>类有必须含有的元素，例如<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.CharField" target="_blank" class="djg-link2">CharField</a>，必须给它指定<span class="djg-pre">max_length</span>。这个不仅会用在数据库schema里，也会用在数据验证里，我们很快会看到。</p>
<p>最后，注意到用<a href="https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey" target="_blank" class="djg-link2">ForeignKey</a>定义了一个关系。它告诉Django每个Choice会关联到一个Poll。Django支持所有的常用数据库关系：多对已，多对多和一对一。</p>
<h2 class="djg-title">激活模型</h2>
<p>Django通过模型里的那点代码获取到了很多的信息，通过这些信息，Django可以：</p>
<ul>
<li>为应用创建数据库模式(<span class="djg-pre">CREATE TABLE</span>语句) 。</li>
<li>创建访问Poll和Choice对象的Python数据库访问API。</li>
</ul>
<p>但是首先我们先要让我们的项目知道<span class="djg-pre">polls</span>这个应用已经安装。</p>
<div class="djg-c">
<p class="djg-c-t">原理（Philosophy）</p>
<p class="djg-c-c">Django应用是“可插拔式”的：你可以把一个应用用在多个项目里，也可以把它发布出去，因为它们不是跟Django的安装绑定的。</p>
</div>
<p>再次编辑文件<span class="djg-pre">settings.py</span>，修改设置<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>包含字符串<span class="djg-pre">'polls'</span>。修改后的配置如下：</p>
<pre class="brush: text; gutter: false">INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'polls'
)</pre>
<p>现在Django知道要去包含<span class="djg-pre">polls</span>这个应用了。我们执行另外一个命令：</p>
<pre class="brush: bash; gutter: false">python manage.py sql polls</pre>
<p>你应该会看到类似下面的东西(poll应用的<span class="djg-pre">CREATE TABLE</span> SQL语句):</p>
<pre class="brush: bash; gutter: false">BEGIN;
CREATE TABLE "polls_poll" (
    "id" integer NOT NULL PRIMARY KEY,
    "question" varchar(200) NOT NULL,
    "pub_date" datetime NOT NULL
)
;
CREATE TABLE "polls_choice" (
    "id" integer NOT NULL PRIMARY KEY,
    "poll_id" integer NOT NULL REFERENCES "polls_poll" ("id"),
    "choice" varchar(200) NOT NULL,
    "votes" integer NOT NULL
)
;
COMMIT;</pre>
<p>注意以下几点：</p>
<ul>
<li>实际输出的东西会根据你所使用的数据库而不同。</li>
<li>表名由应用的名称(<span class="djg-pre">polls</span>)和小写的模型名称 —— <span class="djg-pre">poll</span>和<span class="djg-pre">choice</span>组合而成并自动生成。(你可以重写这个规则。)</li>
<li>主键(IDs)会被自动添加。(同样能可以重写它)</li>
<li>为了方便，Django会添加<span class="djg-pre">"_id"</span>到外键的字段名上。对的，你同样可以重写它。</li>
<li>外键关系由一个<span class="djg-pre">REFERENCES</span>语句明确确定。</li>
<li>它是为你所使用的数据库量身定制的，所以数据库特有的字段如<span class="djg-pre">auto_increment</span> (MySQL)，<span class="djg-pre">serial</span> (PostgreSQL)或者<span class="djg-pre">integer primary key</span> (SQLite)都会自动帮你处理，同样字段名的引号也是如此，例如，是使用双引号还是单引号。这篇教程的作者使用PostgreSQL，所以例子里的输出都是PostgreSQL语法。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sql" target="_blank" class="djg-link2">sql</a>命令并不会在你的数据库里执行SQL语句，它只是在屏幕上显示给你看Django认为需要执行哪些SQL语句。如果你想的话，可以复制和粘贴这些SQL语句到你的数据库命令行。但是，我们很快就能看到，Django提供了一个更为简单的方式去执行这些SQL到数据库上。</li>
</ul>
<p>如果你有兴趣，可以运行以下的命令：</p>
<ul>
<li><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-validate" target="_blank" class="djg-link2">python manage.py validate</a> -- 检查模型的结构错误。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlcustom" target="_blank" class="djg-link2">python manage.py sqlcustom polls</a> -- 输出所有为应用定义的<a href="https://docs.djangoproject.com/en/dev/howto/initial-data/#initial-sql" target="_blank" class="djg-link">自定义SQL语句</a> (例如表修改和约束)。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlclear" target="_blank" class="djg-link2">python manage.py sqlclear polls</a> -- 根据已存在的表，输出这个应用中必要的<span class="djg-pre">DROP TABLE</span>语句。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlindexes" target="_blank" class="djg-link2">python manage.py sqlindexes polls</a> -- 输出这个应用<span class="djg-pre">CREATE INDEX</span>语句。</li>
<li><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlall" target="_blank" class="djg-link2">python manage.py sqlall polls</a> -- <a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sql" target="_blank" class="djg-link2">sql</a>，<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlcustom" target="_blank" class="djg-link2">sqlcustom</a>和<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlindexes" target="_blank" class="djg-link2">sqlindexes</a>命令产生的SQL语句的结合。</li>
</ul>
<p>看这些命令的输出可以帮助你更好的理解底层发生的事情。</p>
<p>现在，再次运行<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-syncdb" target="_blank" class="djg-link2">syncdb</a>在数据库上创建这些模型的表：</p>
<pre class="brush: bash; gutter: false">python manage.py syncdb</pre>
<p><a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-syncdb" target="_blank" class="djg-link2">syncdb</a>命令会为在<a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>里没有相应数据表的应用运行'sqlall'产生的sql语句。它会为自上次运行syncdb以来新添加的应用建立所有的表，初始化数据和索引。你可以在任何时候执行<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-syncdb" target="_blank" class="djg-link2">syncdb</a>，它只会创建不存在的表。</p>
<p>阅读<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/" target="_blank" class="djg-link">django-admin.py documentation</a>以获取<span class="djg-pre">manage.py</span>这个工具所能做的事情的所有信息。</p>
<h2 class="djg-title">使用API</h2>
<p>现在，我们进入Python的交互shell并使用Django提供的API。要进入Python shell，使用这个命令：</p>
<pre class="brush: bash; gutter: false">python manage.py shell</pre>
<p>我在这里遇到了ImportError: No module named readline这个错误，解决方法是下载readline，编译安装，然后重新编译和安装python就可以解决这个问题。</p>
<p>我们使用这个而不是简单的输入"python"，是因为<span class="djg-pre">manage.py</span>会帮你创建项目的环境。“创建项目环境”包括两件事：</p>
<ul>
<li>把<span class="djg-pre">polls</span>添加到<span class="djg-pre">sys.path</span>里。为了灵活性，Django中的模块通过加点的路径来引用(例如<span class="djg-pre">'polls.models'</span>)。为了实现这个，<span class="djg-pre">polls</span>包必须在<span class="djg-pre">sys.path</span>里。<br />我们已经遇到过一个这样的例子了：the <a href="https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS" target="_blank" class="djg-link2">INSTALLED_APPS</a>设置里的列表就是使用点路径表示法。</li>
<li>设置<span class="djg-pre">DJANGO_SETTINGS_MODULE</span>环境变量，为Django指定了你的<span class="djg-pre">settings.py</span>文件的路径。</li>
</ul>
<div class="djg-c">
<p class="djg-c-t">省略manage.py</p>
<p class="djg-c-c">如果你不想使用<span class="djg-pre">manage.py</span>，没问题，只需要确保<span class="djg-pre">mysite</span>和<span class="djg-pre">polls</span> 位于Python路径的根目录 (例如<span class="djg-pre">import mysite</span>和<span class="djg-pre">import polls</span>能正常工作)以及把<span class="djg-pre">DJANGO_SETTINGS_MODULE</span>这个环境变量设置到<span class="djg-pre">mysite.settings</span>里。</p>
<p class="djg-c-c">更多的信息可以查看<a href="https://docs.djangoproject.com/en/dev/ref/django-admin/" target="_blank" class="djg-link">django-admin.py documentation</a>.</p>
</div>
<p>在shell里面时，可以探索下<a href="https://docs.djangoproject.com/en/dev/topics/db/queries/" target="_blank" class="djg-link">database API</a>：</p>
<pre class="brush: python; gutter: false">&gt;&gt;&gt; from polls.models import Poll, Choice # 导入我们刚写的模型。

# 系统里还没有poll。
&gt;&gt;&gt; Poll.objects.all()
[]

# 创建一个新poll。
&gt;&gt;&gt; import datetime
&gt;&gt;&gt; p = Poll(question="What's up?", pub_date=datetime.datetime.now())

# 把对象保存进数据库里。你需要明确调用save()。
&gt;&gt;&gt; p.save()

# 现在它有个ID了。注意这里也可能显示1L而不是1，
# 取决于你使用的是什么数据库。不过这没什么打不了，
# 它只是表明数据库更倾向于把整型返回成Python的长整型对象。
&gt;&gt;&gt; p.id
1

# 通过Python属性访问数据库列
&gt;&gt;&gt; p.question
"What's up?"
&gt;&gt;&gt; p.pub_date
datetime.datetime(2007, 7, 15, 12, 00, 53)

# 通过修改属性来修改值，然后调用save()。
&gt;&gt;&gt; p.pub_date = datetime.datetime(2007, 4, 1, 0, 0)
&gt;&gt;&gt; p.save()

# objects.all()显示数据库所有的poll。
&gt;&gt;&gt; Poll.objects.all()
[&lt;Poll: Poll object&gt;]</pre>
<p>等等。<span class="djg-pre">&lt;Poll: Poll object&gt;</span>完全不能表现出这个对象的有用信息。我们可以编辑poll模型(在文件<span class="djg-pre">polls/models.py</span> 里)添加一个<a href="https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.__unicode__" target="_blank" class="djg-link2">__unicode__()</a>方法给<span class="djg-pre">Poll</span>和<span class="djg-pre">Choice</span>来解决这个问题：</p>
<pre class="brush: python; gutter: false">class Poll(models.Model):
    # ...
    def __unicode__(self):
        return self.question

class Choice(models.Model):
    # ...
    def __unicode__(self):
        return self.choice</pre>
<p>为你的模型添加<a href="https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.__unicode__" target="_blank" class="djg-link2">__unicode__()</a>方法是很有必要的。不仅是为了让你自己更好理解交互提示，而且因为对象的表现的使用会贯穿于整个Django自动产生的admin。</p>
<div class="djg-c">
<p class="djg-c-t">为什么是<a href="https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.__unicode__" target="_blank" class="djg-link2">__unicode__()</a>而不是<a href="https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.__str__" target="_blank" class="djg-link2">__str__()</a>？</p>
<p class="djg-c-c">如果你熟悉Python的话，你可能会更喜欢在你的类里添加__str__()而不是__unicode__()方法。我们这里使用__unicode__()是因为Django模型默认使用Unicode，所有保存在数据库的数据在返回的时候都会转化成Unicode。</p>
<p class="djg-c-c">Django模型有一个默认的__str__()方法叫__unicode__()，它会把结果集转化成UTF-8字节字符串。在各个意味着<span class="djg-pre">unicode(p)</span>会返回一个Unicode字符串，而<span class="djg-pre">str(p)</span>会返回一个普通的UTF-8编码的字符串。</p>
<p class="djg-c-c">如果你还是觉得莫名其妙的话，记得把__unicode__()方法加进你的模型中就行了，幸运的话，一切都会如你所想工作。</p>
</div>
<p>注意这些都是普通的Python方法，让我们添加一个自定义的方法，用作示范：</p>
<pre class="brush: python; gutter: false">import datetime
# ...
class Poll(models.Model):
    # ...
    def was_published_today(self):
        return self.pub_date.date() == datetime.date.today()</pre>
<p>添加<span class="djg-pre">import datetime</span>会引用Python的<span class="djg-pre">datetime</span>模块。</p>
<p>保存这些修改然后再次运行<span class="djg-pre">python manage.py shell</span>新开一个Python交互shell：</p>
<pre class="brush: python; gutter: false">&gt;&gt;&gt; from polls.models import Poll, Choice

# 确保我们新加的__unicode__()正常工作。
&gt;&gt;&gt; Poll.objects.all()
[&lt;Poll: What's up?&gt;]

# Django提供了丰富的数据库查找API，
# 这些API完全由关键字参数驱动。
&gt;&gt;&gt; Poll.objects.filter(id=1)
[&lt;Poll: What's up?&gt;]
&gt;&gt;&gt; Poll.objects.filter(question__startswith='What')
[&lt;Poll: What's up?&gt;]

# 获取年份为2007的poll。
&gt;&gt;&gt; Poll.objects.get(pub_date__year=2007)
&lt;Poll: What's up?&gt;

&gt;&gt;&gt; Poll.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Poll matching query does not exist.

# 通过主键查找是很常用的一种方式，
# 所以Django为这种查找方法提供一个便捷的方式。
# 下面相当于Poll.objects.get(id=1)。
&gt;&gt;&gt; Poll.objects.get(pk=1)
&lt;Poll: What's up?&gt;

# 确认我们的自定义方法正常工作。
&gt;&gt;&gt; p = Poll.objects.get(pk=1)
&gt;&gt;&gt; p.was_published_today()
False

# 给Poll加上几个Choice。
# 调用创建函数创建choice对象，会调用INSERT语句，
# 把choice添加到已存choice的集合里，并返回这个新创建的Choice对象。
# Django会创建一个集合保存外键关联，这个集合可以通过API进行访问。
&gt;&gt;&gt; p = Poll.objects.get(pk=1)

# 显示关联的所有choice —— 目前还没有。
&gt;&gt;&gt; p.choice_set.all()
[]

# 创建三个choice。
&gt;&gt;&gt; p.choice_set.create(choice='Not much', votes=0)
&lt;Choice: Not much&gt;
&gt;&gt;&gt; p.choice_set.create(choice='The sky', votes=0)
&lt;Choice: The sky&gt;
&gt;&gt;&gt; c = p.choice_set.create(choice='Just hacking again', votes=0)

# Choice对象可通过API访问它关联的Poll对象。
&gt;&gt;&gt; c.poll
&lt;Poll: What's up?&gt;

# 反之亦然：Poll对象访问Choice对象。
&gt;&gt;&gt; p.choice_set.all()
[&lt;Choice: Not much&gt;, &lt;Choice: The sky&gt;, &lt;Choice: Just hacking again&gt;]
&gt;&gt;&gt; p.choice_set.count()
3

# API会根据你的需要自动追踪关系。
# 使用双下划线分隔这些关系。
# 你可以深入到任意的层级，这个没有限制。
# 查找pub_date在2007年的poll的所有choice。
&gt;&gt;&gt; Choice.objects.filter(poll__pub_date__year=2007)
[&lt;Choice: Not much&gt;, &lt;Choice: The sky&gt;, &lt;Choice: Just hacking again&gt;]

# 我们使用delete()删除一个choice。
&gt;&gt;&gt; c = p.choice_set.filter(choice__startswith='Just hacking')
&gt;&gt;&gt; c.delete()</pre>
<p>更多关于模型关系的信息，可以看<a href="https://docs.djangoproject.com/en/dev/ref/models/relations/" target="_blank" class="djg-link">Accessing related objects</a>。想知道更多关于如何使用双下划线通过API实现字段的查找，看<a href="http://docs.djangoproject.com/en/1.2/topics/db/queries/#field-lookups" target="_blank" class="djg-link">Field lookups</a>。要获取数据库API的所有信息，请看<a href="https://docs.djangoproject.com/en/dev/topics/db/queries/" target="_blank" class="djg-link">Database API reference</a>。</p>
<p>好吧，开篇就到这里，更详细请查看：</p>
<p><a href="https://docs.djangoproject.com/en/dev/intro/tutorial01/" target="_blank">https://docs.djangoproject.com/en/dev/intro/tutorial01/</a></p>
<p>Written by icyfire @ 2011.08.05 updated @ 2011.10.09</p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/10/writing_your_first_django_app/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MongoDB与内存</title>
		<link>http://www.icyfire.me/2011/08/mongodb-and-memroy/</link>
		<comments>http://www.icyfire.me/2011/08/mongodb-and-memroy/#comments</comments>
		<pubDate>Sun, 21 Aug 2011 12:44:28 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[MongoDB]]></category>
		<category><![CDATA[NoSQL]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1157</guid>
		<description><![CDATA[但凡初次接触MongoDB的人，无不惊讶于它对内存的贪得无厌，至于个中缘由，我先讲讲Linux是如何管理内存的，再说说MongoDB是如何使用内存的，答案自然就清楚了。

<img src="http://www.icyfire.me/wp-content/uploads/2011/08/79_110607152750_1-640x213.png" alt="" title="79_110607152750_1" width="640" height="213" class="aligncenter size-medium wp-image-1158" /> <a href="http://www.icyfire.me/2011/08/mongodb-and-memroy/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>但凡初次接触MongoDB的人，无不惊讶于它对内存的贪得无厌，至于个中缘由，我先讲讲Linux是如何管理内存的，再说说MongoDB是如何使用内存的，答案自然就清楚了。</p>
<p>据说带着问题学习更有效，那就先看一个MongoDB服务器的top命令结果：</p>
<pre class="brush: shell; gutter: true">shell&gt; top -p $(pidof mongod)
Mem:  32872124k total, 30065320k used,  2806804k free,   245020k buffers
Swap:  2097144k total,      100k used,  2097044k free, 26482048k cached

 VIRT  RES  SHR %MEM
1892g  21g  21g 69.6</pre>
<p>这台MongoDB服务器有没有性能问题？大家可以一边思考一边继续阅读。</p>
<h3>先讲讲Linux是如何管理内存的</h3>
<p>在Linux里（别的系统也差不多），内存有物理内存和<a href="http://en.wikipedia.org/wiki/Virtual_memory" target="_blank">虚拟内存</a>之说，物理内存是什么自然无需解释，虚拟内存实际是物理内存的抽象，多数情况下，出于方便性的考虑，程序访问的都是虚拟内存地址，然后操作系统会把它翻译成物理内存地址。</p>
<p>很多人会把虚拟内存和Swap混为一谈，实际上Swap只是虚拟内存引申出的一种技术而已：操作系统一旦物理内存不足，为了腾出内存空间存放新内容，就会把当前物理内存中的内容放到交换分区里，稍后用到的时候再取回来，需要注意的是，Swap的使用可能会带来性能问题，偶尔为之无需紧张，糟糕的是物理内存和交换分区频繁的发生数据交换，这被称之为Swap颠簸，一旦发生这种情况，先要明确是什么原因造成的，如果是内存不足就好办了，加内存就可以解决，不过有的时候即使内存充足也可能会出现这种问题，比如MySQL就有可能出现这样的情况，解决方法是限制使用Swap：</p>
<pre class="brush: shell; gutter: true">shell&gt; sysctl -w vm.swappiness=0</pre>
<p>查看内存情况最常用的是free命令：</p>
<pre class="brush: shell; gutter: true">shell&gt; free -m
             total       used       free     shared    buffers     cached
Mem:         32101      29377       2723          0        239      25880
-/+ buffers/cache:       3258      28842
Swap:         2047          0       2047</pre>
<p>新手看到used一栏数值偏大，free一栏数值偏小，往往会认为内存要用光了。其实并非如此，之所以这样是因为每当我们操作文件的时候，Linux都会尽可能的把文件缓存到内存里，这样下次访问的时候，就可以直接从内存中取结果，所以cached一栏的数值非常的大，不过不用担心，这部分内存是可回收的，操作系统会按照<a href="http://en.wikipedia.org/wiki/Cache_algorithms" target="_blank">LRU</a>算法淘汰冷数据。还有一个buffers，也是可回收的，不过它是保留给块设备使用的。</p>
<p>知道了原理，我们就可以推算出系统可用的内存是free + buffers + cached：</p>
<pre class="brush: shell; gutter: true">shell&gt; echo "29377 - 239 - 25880" | bc -l
3258</pre>
<p>除了free命令，还可以使用sar命令：</p>
<pre class="brush: shell; gutter: true">shell&gt; sar -r
kbmemfree kbmemused  %memused kbbuffers  kbcached
  3224392  29647732     90.19    246116  26070160

shell&gt; sar -W
pswpin/s pswpout/s
    0.00      0.00</pre>
<p>希望你没有被%memused吓到，如果不幸言中，重读本文。</p>
<h3>再说说MongoDB是如何使用内存的</h3>
<p>目前，MongoDB使用的是<a href="http://www.mongodb.org/display/DOCS/Caching" target="_blank">内存映射存储引擎</a>，它会把磁盘IO操作转换成内存操作，如果是读操作，内存中的数据起到缓存的作用，如果是写操作，内存还可以把随机的写操作转换成顺序的写操作，总之可以大幅度提升性能。MongoDB并不干涉内存管理工作，而是把这些工作留给操作系统的虚拟内存管理器去处理，这样的好处是简化了MongoDB的工作，但坏处是你没有方法很方便的控制MongoDB占多大内存，事实上MongoDB会占用所有能用的内存，所以最好不要把别的服务和MongoDB放一起。</p>
<p>有时候，即便MongoDB使用的是64位操作系统，也可能会遭遇臭名昭著的<a href="http://en.wikipedia.org/wiki/Out_of_memory" target="_blank">OOM</a>问题，出现这种情况，多半是因为限制了虚拟内存的大小所致，可以这样查看当前值：</p>
<pre class="brush: shell; gutter: true">shell&gt; ulimit -a | grep 'virtual'</pre>
<p>多数操作系统缺省都是把它设置成unlimited的，如果你的操作系统不是，可以这样修改：</p>
<pre class="brush: shell; gutter: true">shell&gt; ulimit -v unlimited</pre>
<p>有时候，MongoDB连接数过多的话，也可能影响性能，连接数可以这样查询：</p>
<pre class="brush: shell; gutter: true">mongo&gt; db.serverStatus().connections</pre>
<p>每个连接都是一个线程，需要一个Stack，而Linux下缺省的Stack设置一般比较大：</p>
<pre class="brush: shell; gutter: true"># ulimit -a | grep stack
stack size              (kbytes, -s) 10240</pre>
<p>所有连接消耗的内存加起来会相当惊人，推荐把Stack设置小一点，比如说1024：</p>
<pre class="brush: shell; gutter: true">shell&gt; ulimit -s 1024</pre>
<p>注：ulimit的使用是有上下文的，最好放在MongoDB的启动脚本里。</p>
<p>有时候，出于某些原因，你可能想释放掉MongoDB占用的内存，不过前面说了，内存管理工作是由虚拟内存管理器控制的，所以通常你只能通过重启服务来释放内存，你一定不齿于这样的方法，幸好可以使用MongoDB内置的<a href="http://www.mongodb.org/display/DOCS/List+of+Database+Commands" target="_blank">closeAllDatabases</a>命令达到目的：</p>
<pre class="brush: shell; gutter: true">mongo&gt; use admin
mongo&gt; db.runCommand({closeAllDatabases:1})</pre>
<p>另外，通过调整内核参数drop_caches也可以释放缓存：</p>
<pre class="brush: shell; gutter: true">shell&gt; sysctl -w vm.drop_caches=1</pre>
<p>平时可以通过mongo命令行来监控MongoDB的内存使用情况，如下所示：</p>
<pre class="brush: shell; gutter: true">mongo&gt; db.serverStatus().mem:
{
    "resident" : 22346,
    "virtual" : 1938524,
    "mapped" : 962283
}</pre>
<p>还可以通过mongostat命令来监控MongoDB的内存使用情况，如下所示：</p>
<pre class="brush: shell; gutter: true">shell&gt; mongostat
mapped  vsize    res faults
  940g  1893g  21.9g      0</pre>
<p>其中内存相关字段的含义是：</p>
<ul>
<li>mapped：映射到内存的数据大小</li>
<li>visze：占用的虚拟内存大小</li>
<li>res：占用的物理内存大小</li>
</ul>
<p>注：如果操作不能在内存中完成，结果faults列的数值不会是0，视大小可能有性能问题。</p>
<p>在上面的结果中，vsize是mapped的两倍，而mapped等于数据文件的大小，所以说vsize是数据文件的两倍，之所以会这样，是因为本例中，MongoDB开启了<a href="http://www.mongodb.org/display/DOCS/Journaling" target="_blank">journal</a>，需要在内存里多映射一次数据文件，如果关闭journal，则vsize和mapped大致相当。</p>
<p>如果想验证这一点，可以在开启或关闭journal后，通过pmap命令来观察文件映射情况：</p>
<pre class="brush: shell; gutter: true">shell&gt; pmap $(pidof mongod)</pre>
<p>到底MongoDB配备多大内存合适？宽泛点来说，多多益善，如果要确切点来说，这实际取决于你的数据及索引的大小，内存如果能够装下全部数据加索引是最佳情况，不过很多时候，数据都会比内存大，比如本文所涉及的MongoDB实例：</p>
<pre class="brush: shell; gutter: true">mongo&gt; db.stats()
{
    "dataSize" : 1004862191980,
    "indexSize" : 1335929664
}</pre>
<p>本例中索引只有1G多，内存完全能装下，而数据文件则达到了1T，估计很难找到这么大内存，此时保证内存能装下热数据即可，至于热数据是多少，取决于具体的应用。如此一来内存大小就明确了：内存 > 索引 + 热数据，最好有点富余，因为前面提到过，连接本身也要消耗一定的内存，另外，操作系统本身正常运转也需要消耗一部分内存。</p>
<p>关于MongoDB与内存的话题，大家还可以参考<a href="http://www.mongodb.org/display/DOCS/Checking+Server+Memory+Usage" target="_blank">官方文档</a>中的相关介绍。</p>
<p>原文：<a href="http://huoding.com/2011/08/19/107" target="_blank">MongoDB与内存</a><br />
作者：<a href="http://huoding.com/author/laowang" target="_blank">老王</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/08/mongodb-and-memroy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>一致性哈希算法及其在分布式系统中的应用</title>
		<link>http://www.icyfire.me/2011/08/consistent-hash-intro/</link>
		<comments>http://www.icyfire.me/2011/08/consistent-hash-intro/#comments</comments>
		<pubDate>Thu, 11 Aug 2011 12:39:04 +0000</pubDate>
		<dc:creator>icyfire</dc:creator>
				<category><![CDATA[网站前端]]></category>
		<category><![CDATA[Memcached]]></category>
		<category><![CDATA[NoSQL]]></category>

		<guid isPermaLink="false">http://www.icyfire.me/?p=1147</guid>
		<description><![CDATA[<img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-00.png" alt="" title="" width="538" height="346" class="aligncenter size-full wp-image-1148" />

本文将会从实际应用场景出发，介绍一致性哈希算法（Consistent Hash）及其在分布式系统中的应用。首先本文会描述一个在日常开发中经常会遇到的问题场景，借此介绍一致性哈希算法以及这个算法如何解决此问题；接下来会对这个算法进行相对详细的描述，并讨论一些如虚拟节点等与此算法应用相关的话题。 <a href="http://www.icyfire.me/2011/08/consistent-hash-intro/">继续阅读 <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h1>摘要</h1>
<p>本文将会从实际应用场景出发，介绍一致性哈希算法（Consistent Hash）及其在分布式系统中的应用。首先本文会描述一个在日常开发中经常会遇到的问题场景，借此介绍一致性哈希算法以及这个算法如何解决此问题；接下来会对这个算法进行相对详细的描述，并讨论一些如虚拟节点等与此算法应用相关的话题。</p>
<h1>分布式缓存问题</h1>
<p>假设我们有一个网站，最近发现随着流量增加，服务器压力越来越大，之前直接读写数据库的方式不太给力了，于是我们想引入Memcached作为缓存机制。现在我们一共有三台机器可以作为Memcached服务器，如下图所示。</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-00.png" alt="" title="" width="538" height="346" class="aligncenter size-full wp-image-1148" /></p>
<p>很显然，最简单的策略是将每一次Memcached请求随机发送到一台Memcached服务器，但是这种策略可能会带来两个问题：一是同一份数据可能被存在不同的机器上而造成数据冗余，二是有可能某数据已经被缓存但是访问却没有命中，因为无法保证对相同key的所有访问都被发送到相同的服务器。因此，随机策略无论是时间效率还是空间效率都非常不好。</p>
<p>要解决上述问题只需做到如下一点：保证对相同key的访问会被发送到相同的服务器。很多方法可以实现这一点，最常用的方法是计算哈希。例如对于每次访问，可以按如下算法计算其哈希值：</p>
<p>h = Hash(key) % 3</p>
<p>其中Hash是一个从字符串到正整数的哈希映射函数。这样，如果我们将Memcached Server分别编号为0、1、2，那么就可以根据上式和key计算出服务器编号h，然后去访问。</p>
<p>这个方法虽然解决了上面提到的两个问题，但是存在一些其它的问题。如果将上述方法抽象，可以认为通过：</p>
<p>h = Hash(key) % N</p>
<p>这个算式计算每个key的请求应该被发送到哪台服务器，其中N为服务器的台数，并且服务器按照0 – (N-1)编号。</p>
<p>这个算法的问题在于容错性和扩展性不好。所谓容错性是指当系统中某一个或几个服务器变得不可用时，整个系统是否可以正确高效运行；而扩展性是指当加入新的服务器后，整个系统是否可以正确高效运行。</p>
<p>现假设有一台服务器宕机了，那么为了填补空缺，要将宕机的服务器从编号列表中移除，后面的服务器按顺序前移一位并将其编号值减一，此时每个key就要按h = Hash(key) % (N-1)重新计算；同样，如果新增了一台服务器，虽然原有服务器编号不用改变，但是要按h = Hash(key) % (N+1)重新计算哈希值。因此系统中一旦有服务器变更，大量的key会被重定位到不同的服务器从而造成大量的缓存不命中。而这种情况在分布式系统中是非常糟糕的。</p>
<p>一个设计良好的分布式哈希方案应该具有良好的单调性，即服务节点的增减不会造成大量哈希重定位。一致性哈希算法就是这样一种哈希方案。</p>
<h1>一致性哈希算法</h1>
<h2>算法简述</h2>
<p>一致性哈希算法（Consistent Hash）最早在论文《<a href="http://www.akamai.com/dl/technical_publications/ConsistenHashingandRandomTreesDistributedCachingprotocolsforrelievingHotSpotsontheworldwideweb.pdf" target="_blank">Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web</a>》中被提出。简单来说，一致性哈希将整个哈希值空间组织成一个虚拟的圆环，如假设某哈希函数H的值空间为0 - 2<sup>32</sup>-1（即哈希值是一个32位无符号整形），整个哈希空间环如下：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-01.png" alt="" title="" width="229" height="246" class="aligncenter size-full wp-image-1149" /></p>
<p>整个空间按顺时针方向组织。0和2<sup>32</sup>-1在零点中方向重合。</p>
<p>下一步将各个服务器使用H进行一个哈希，具体可以选择服务器的ip或主机名作为关键字进行哈希，这样每台机器就能确定其在哈希环上的位置，这里假设将上文中三台服务器使用ip地址哈希后在环空间的位置如下：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-02.png" alt="" title="" width="298" height="248" class="aligncenter size-full wp-image-1150" /></p>
<p>接下来使用如下算法定位数据访问到相应服务器：将数据key使用相同的函数H计算出哈希值h，通根据h确定此数据在环上的位置，从此位置沿环顺时针“行走”，第一台遇到的服务器就是其应该定位到的服务器。</p>
<p>例如我们有A、B、C、D四个数据对象，经过哈希计算后，在环空间上的位置如下：<br />
<img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-03.png" alt="" title="" width="299" height="259" class="aligncenter size-full wp-image-1151" /></p>
<p>根据一致性哈希算法，数据A会被定为到Server 1上，D被定为到Server 3上，而B、C分别被定为到Server 2上。</p>
<h2>容错性与可扩展性分析</h2>
<p>下面分析一致性哈希算法的容错性和可扩展性。现假设Server 3宕机了：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-04.png" alt="" title="" width="299" height="259" class="aligncenter size-full wp-image-1152" /></p>
<p>可以看到此时A、C、B不会受到影响，只有D节点被重定位到Server 2。一般的，在一致性哈希算法中，如果一台服务器不可用，则受影响的数据仅仅是此服务器到其环空间中前一台服务器（即顺着逆时针方向行走遇到的第一台服务器）之间数据，其它不会受到影响。</p>
<p>下面考虑另外一种情况，如果我们在系统中增加一台服务器Memcached Server 4：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-05.png" alt="" title="" width="299" height="259" class="aligncenter size-full wp-image-1153" /></p>
<p>此时A、D、C不受影响，只有B需要重定位到新的Server 4。一般的，在一致性哈希算法中，如果增加一台服务器，则受影响的数据仅仅是新服务器到其环空间中前一台服务器（即顺着逆时针方向行走遇到的第一台服务器）之间数据，其它不会受到影响。</p>
<p>综上所述，一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据，具有较好的容错性和可扩展性。</p>
<h2>虚拟节点</h2>
<p>一致性哈希算法在服务节点太少时，容易因为节点分部不均匀而造成数据倾斜问题。例如我们的系统中有两台服务器，其环分布如下：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-06.png" alt="" title="" width="285" height="246" class="aligncenter size-full wp-image-1154" /></p>
<p>此时必然造成大量数据集中到Server 1上，而只有极少量会定位到Server 2上。为了解决这种数据倾斜问题，一致性哈希算法引入了虚拟节点机制，即对每一个服务节点计算多个哈希，每个计算结果位置都放置一个此服务节点，称为虚拟节点。具体做法可以在服务器ip或主机名的后面增加编号来实现。例如上面的情况，我们决定为每台服务器计算三个虚拟节点，于是可以分别计算“Memcached Server 1#1”、“Memcached Server 1#2”、“Memcached Server 1#3”、“Memcached Server 2#1”、“Memcached Server 2#2”、“Memcached Server 2#3”的哈希值，于是形成六个虚拟节点：</p>
<p><img src="http://www.icyfire.me/wp-content/uploads/2011/08/consistent-hash-intro-07.png" alt="" title="" width="341" height="265" class="aligncenter size-full wp-image-1155" /></p>
<p>同时数据定位算法不变，只是多了一步虚拟节点到实际节点的映射，例如定位到“Memcached Server 1#1”、“Memcached Server 1#2”、“Memcached Server 1#3”三个虚拟节点的数据均定位到Server 1上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中，通常将虚拟节点数设置为32甚至更大，因此即使很少的服务节点也能做到相对均匀的数据分布。</p>
<h1>总结</h1>
<p>目前一致性哈希基本成为了分布式系统组件的标准配置，例如Memcached的各种客户端都提供内置的一致性哈希支持。本文只是简要介绍了这个算法，更深入的内容可以参看论文《<a href="http://www.akamai.com/dl/technical_publications/ConsistenHashingandRandomTreesDistributedCachingprotocolsforrelievingHotSpotsontheworldwideweb.pdf" target="_blank">Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web</a>》，同时提供一个<a href="http://www.codeproject.com/KB/recipes/lib-conhash.aspx" target="_blank">C语言版本的实现</a>供参考。</p>
<p>原文:<a href="http://www.cnblogs.com/leoo2sk/archive/2011/08/11/consistent-hash-intro.html" target="_blank">http://www.cnblogs.com/leoo2sk/archive/2011/08/11/consistent-hash-intro.html</a><br />
作者:<a href="http://leoo2sk.cnblogs.com/" target="_blank">张洋</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.icyfire.me/2011/08/consistent-hash-intro/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

