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

<channel>
	<title>绿色记忆 &#187; Django</title>
	<atom:link href="https://blog.gmem.cc/tag/django/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Graphite学习笔记</title>
		<link>https://blog.gmem.cc/graphite-study-note</link>
		<comments>https://blog.gmem.cc/graphite-study-note#comments</comments>
		<pubDate>Mon, 11 Jul 2016 08:33:51 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Database]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[TSDB]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=12245</guid>
		<description><![CDATA[<p>简介 Graphite是一个开源项目，可以作为时间序列数据库（TSDB）使用，当你需要存储随着时间变化的数值时，应当考虑使用时间序列数据库。 除了数据的存储、查询外，Graphite还提供数据可视化（UI层）功能，它可以很好的在廉价的硬件上运行。你可以使用Graphite来监控网站、应用程序、网络服务器等的性能数据（Metrics），轻松实现基于时间维的分析。 Graphite本身不负责性能数据的采集，但是它提供了简单易用的接口，公共这些接口你可以把基于数字的性能数据存储到Graphite中。 术语 术语 说明 datapoint 数据点，存放在timestamp bucket中的数值。timestamp bucket中的默认值是None function 时间序列（ time-series）函数，用来转换、合并、计算多个series resolution 分辨率，也称precision。序列中，一个数据点所跨越（代表）的秒数。分辨率确定了存储数据点频率如果一个series每N秒存储一个数据点，则其分辨率为N retention 驻留，series中包含的数据点的个数 series 一已命名的数据点的集合，每个series由其名称唯一确定，名称由点号分隔的字符串组成也称为Metrics、Metric series series <a class="read-more" href="https://blog.gmem.cc/graphite-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/graphite-study-note">Graphite学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p><a href="http://graphiteapp.org/">Graphite</a>是一个开源项目，可以作为时间序列数据库（TSDB）使用，当你需要存储<span style="background-color: #c0c0c0;">随着时间变化的数值</span>时，应当考虑使用时间序列数据库。</p>
<p>除了<span style="background-color: #c0c0c0;">数据的存储、查询</span>外，Graphite还提供<span style="background-color: #c0c0c0;">数据可视化</span>（UI层）功能，它可以很好的在廉价的硬件上运行。你可以使用Graphite来监控网站、应用程序、网络服务器等的性能数据（Metrics），轻松实现基于时间维的分析。</p>
<p>Graphite本身不负责性能数据的采集，但是它提供了简单易用的接口，公共这些接口你可以把基于数字的性能数据存储到Graphite中。</p>
<div class="blog_h2"><span class="graybg">术语</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>datapoint</td>
<td>数据点，存放在timestamp bucket中的数值。timestamp bucket中的默认值是None</td>
</tr>
<tr>
<td>function</td>
<td>时间序列（ time-series）函数，用来转换、合并、计算多个series</td>
</tr>
<tr>
<td>resolution</td>
<td>分辨率，也称precision。序列中，一个数据点所跨越（代表）的秒数。分辨率确定了存储数据点频率<br />如果一个series每N秒存储一个数据点，则其分辨率为N</td>
</tr>
<tr>
<td>retention</td>
<td>驻留，series中包含的数据点的个数</td>
</tr>
<tr>
<td>series</td>
<td>一已命名的数据点的集合，每个series由其名称唯一确定，名称由点号分隔的字符串组成<br />也称为Metrics、Metric series</td>
</tr>
<tr>
<td>series list</td>
<td>包含通配符的series名称，匹配多个series</td>
</tr>
<tr>
<td>target</td>
<td>图形展示时的数据源，可以是metrics名称、metrics通配符、或者基于前两者的函数调用表达式</td>
</tr>
<tr>
<td>timestamp</td>
<td>数据点所关联的时间，1970-01-01到产生数据点那一刻的秒数</td>
</tr>
<tr>
<td>timestamp bucket</td>
<td>经过舍入后，能够整除分辨率的timestamp</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p>Graphite由三个组件构成：</p>
<ol>
<li>Carbon：一个基于<a href="https://twistedmatrix.com/">Twisted</a>（Python事件驱动网络框架）的守护程序，负责监听外部的时间序列数据</li>
<li>Whisper：一个简单的存储时间序列数据的数据库，设计上和<a href="http://oss.oetiker.ch/rrdtool/">RRDtool</a>类似 </li>
<li>Graphite-Web：一个基于<a href="https://www.djangoproject.com/">Django</a>（Python Web框架）的Web应用，使用<a href="https://www.cairographics.org/">Cairo</a>（一个2D图形库）来渲染性能数据的图表，使用<a href="https://www.sencha.com/products/extjs/">ExtJS</a>作为基础UI框架</li>
</ol>
<p>下面是Graphite的架构图：<img class="aligncenter size-full wp-image-12262" src="https://blog.gmem.cc/wp-content/uploads/2016/07/graphite-arch.png" alt="graphite-arch" width="100%" /></p>
<p>一旦你把数据送给Carbon，它就立刻可以在Webapp的图表中显示，因为数据在被写入文件系统之前，会驻留在缓存中。</p>
<p>除了使用Graphite Webapp，你也可以通过URL API，将图表嵌入到自己的应用程序中去。如何使用Graphite</p>
<div class="blog_h2"><span class="graybg">如何使用Graphite</span></div>
<p>使用Graphite来监控你的性能数据，你需要完成以下工作：</p>
<ol>
<li>理解Graphite组件的职责和相互关系</li>
<li>安装Graphite及其依赖</li>
<li>基础的配置，让Graphite能运行起来</li>
<li>设计Metrics路径</li>
<li>配置Metrics的驻留规则、聚合规则等</li>
<li>向Graphite发送Metrics</li>
<li>从Graphite获取Metrics并展示</li>
</ol>
<div class="blog_h3"><span class="graybg">Metrics路径设计</span></div>
<p>Metrics路径由点号分隔的字符串构成，类似于Python的包名称。路径是Metrics的标识。</p>
<p>你应当仔细的设计此路径的命名空间，以反映出所有Metrics之间的层次关系。例如servers.zircon.cpu，这个三级路径设计中，第一级表示设备类别，第二级表示设备名称，第三级表示监测点类型。</p>
<div class="blog_h1"><span class="graybg">理解Graphite组件</span></div>
<div class="blog_h2"><span class="graybg">Carbon</span></div>
<p>Carbon由一系列的守护进程组成，这些守护进程共同组成Graphite的存储后端。在最小化的安装下，只有一个守护进程carbon-cache.py。根据需要你可以启用carbon-relay.py、carbon-aggregator.py以便实现Metrics分发、定制聚合规则。各Carbon守护进程简介如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">守护进程</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>缓存进程<br />carbon-cache</td>
<td>
<p>缓存进程的程序文件是carbon-cache.py。它通过多种协议接收Metrics，然后尽可能高效的将其写入磁盘。从实现角度来说，缓存进程先把Metrics存放在RAM中，然后定期的通过whisper库进行入库</p>
<p>缓存进程提供一个查询服务供Graphite Webapp使用，用来快速获取位于内存中的Metrics数据点</p>
<p>要定制此进程的行为，可以修改carbon.conf的[cache]段、storage-schemas.conf、storage-aggregation.conf</p>
</td>
</tr>
<tr>
<td>中继进程<br />carbon-relay</td>
<td>
<p>该进程可以担任这两个职责之一：复制（<span style="color: #404040;">replication</span>）和分片（<span style="color: #404040;">sharding</span>）</p>
<p>要定制此进程的行为，可以修改carbon.conf的[relay]段、relay-rules.conf</p>
</td>
</tr>
<tr>
<td>前置聚合进程<br />carbon-aggregator</td>
<td>
<p>前置于缓存进程运行，能够在存入whisper之前，缓冲、聚合Metrics。当不需要细粒度的数据时启用该进程，可以减少I/O和.wsp文件的大小</p>
<p>要定制此进程的行为，可以修改carbon.conf的[aggregator]段、aggregation-rules.conf</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Whisper</span></div>
<p>Whisper是一个固定大小（fixed-size）的数据库，其设计和用途与RRD（round-robin-database）类似。它能提供快速、可靠的数值数据的存储。</p>
<p>Whisper能够对最近的数据进行高分辨率的存储，而对久远的历史数据，自动降低其存储精度（减少样本数量）。</p>
<p>在大多数场景下，Whisper有足够好的性能。它比RRDtool慢，主要原因是Whisper基于Python编写，这个性能差异很小，通常不需要考虑。</p>
<p>Whisper数据库以文件方式存放在磁盘上，扩展名.wsp。Carbon会为每个Metrics创建一个.wsp文件，路径的最后一节作为文件的basename，路径的其它部分形成目录层次。</p>
<div class="blog_h3"><span class="graybg">数据点</span></div>
<p>在Whisper中存储的每一个数值，称为数据点（Data Points）。</p>
<p>在磁盘上，数据点以大端、双精度浮点数存储。每个数据还附带一个时间戳信息，时间戳为1970-01-01到数据采集时间的秒数。</p>
<div class="blog_h3"><span class="graybg">归档</span></div>
<p>一个Whisper数据库文件可以包含一个或者多个“归档”，归档是数据文件中的逻辑段。</p>
<p>每个归档具有不同的数据分辨率（一定时间段内，数据点数量越多，则分辨率越高）。归档以最高分辨率（最小驻留时间） —— 最低分辨率（最长驻留时间）的顺序，在数据库文件中顺序排列。</p>
<p>为了精确的从高分辨率向低分辨率的归档聚合，高分辨率归档和它之后的低分辨率归档，其分辨率应当具有整除关系。例如，第一个归档分辨率为60秒/数据点，那么第二个归档可以是300秒/数据点，第三个归档可以是3600秒/数据点。</p>
<p>一个数据库的总计驻留时间（存放的数据点跨越的时间），由最长驻留时间的（最后一个）归档确定。因为之前的那些归档，时间区间都是它的子区间。</p>
<div class="blog_h3"><span class="graybg">聚合</span></div>
<p>把数据点转移到低分辨率归档时，面临着如何把多个数据点转变为单个数据点的问题。Whisper支持average、sum、last、max、min等聚合函数。</p>
<p>注意，这里的聚合和Carbon提供的前置聚合不是一回事。</p>
<div class="blog_h3"><span class="graybg">多归档的读写策略</span></div>
<p>当Whisper向一个多归档数据写入Metrics时，数据点将被同时写入到所有的归档中。这意味着聚合动作随时可能发生。</p>
<p>当从Whisper中获取数据时，第一个能完整覆盖所需区间的归档被使用。</p>
<div class="blog_h3"><span class="graybg">磁盘空间利用率问题</span></div>
<p>Whisper的磁盘利用效率不高，因为：</p>
<ol>
<li>每个数据点要附加一个时间戳信息</li>
<li>由于归档的时间区间有重叠，因此数据存在冗余</li>
<li>归档中的时间槽位（time-slots）总是占据着磁盘空间，不管有没有值存储在其中</li>
</ol>
<p>这些特征是故意的，主要出于性能方面的考虑。</p>
<div class="blog_h3"><span class="graybg">Whisper与RRD的区别</span></div>
<ol>
<li>RRD不能去填充以前的时间槽位。这意味着每一条数据都必须是更新的，不会“补录”</li>
<li>RRD不能很好的支持不规则的数据更新。如果RRD接收到一条数据，但是后续数据没有到来，则前一条数据可能丢失</li>
</ol>
<div class="blog_h1"><span class="graybg">安装与配置</span></div>
<div class="blog_h2"><span class="graybg">检查并安装依赖</span></div>
<div class="blog_h3"><span class="graybg">检查依赖</span></div>
<pre class="crayon-plain-tag">git clone https://github.com/graphite-project/graphite-web.git
graphite-web/check-dependencies.py
# 根据输出的提示来判断缺少哪些依赖，然后安装</pre>
<div class="blog_h3"><span class="graybg">安装依赖</span></div>
<pre class="crayon-plain-tag">yum -y install python-devel     # Carbon 依赖于 Python Development Headers

yum install pycairo             # Cairo库的Python绑定

pip install django              # Web框架
pip install django-tagging

pip install pytz

yum install fontconfig

yum install -y memcached        # 可选，缓存支持
pip install python-memcached    
                                # 可选，RDDTool
yum install cairo-devel libxml2-devel pango-devel pango libpng-devel freetype freetype-devel libart_lgpl-devel rrdtool-devel

pip install python-rrdtool      # 可选，RRD支持

pip install whitenoise          # 用于Web静态文件处理

yum install pyOpenSSL           # OpenSSL的Python绑定
pip install service_identity    # SSL相关</pre>
<div class="blog_h2"><span class="graybg">通过pip安装Craphite组件</span></div>
<pre class="crayon-plain-tag">pip install https://github.com/graphite-project/ceres/tarball/master
pip install whisper
pip install carbon
pip install graphite-web</pre>
<div class="blog_h3"><span class="graybg">设置目录权限</span></div>
<pre class="crayon-plain-tag">sudo groupadd graphite
sudo usermod -a -G graphite root
sudo usermod -a -G graphite apache
sudo chgrp -R graphite /opt/graphite/storage
sudo chmod -R 770 /opt/graphite/storage</pre>
<div class="blog_h2"><span class="graybg">默认安装布局</span></div>
<p>Whisper被安装到Python全局site-packages目录，另外两个Graphite组件安装到<pre class="crayon-plain-tag">/opt/graphite</pre>，该目录（记为<pre class="crayon-plain-tag">$GRAPHITE_ROOT</pre>  ）的布局如下：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子目录</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>bin</td>
<td>一些脚本</td>
</tr>
<tr>
<td>conf</td>
<td>配置文件</td>
</tr>
<tr>
<td>lib</td>
<td>一些Python依赖库，Carbon的PYTHONPATH</td>
</tr>
<tr>
<td>storage</td>
<td>存储SQLite数据文件。该目录记为<pre class="crayon-plain-tag">$STORAGE_DIR</pre> </td>
</tr>
<tr>
<td>storage/log</td>
<td>Carbon和Graphite-web的日志</td>
</tr>
<tr>
<td>storage/rrd</td>
<td>待读取的RRD文件</td>
</tr>
<tr>
<td>storages/whisper</td>
<td>Whisper数据文件</td>
</tr>
<tr>
<td>webapp</td>
<td>
<p>Graphite-web的Web资源、PYTHONPATH</p>
</td>
</tr>
<tr>
<td>webapp/graphite</td>
<td>
<p>标准的Django工程结构<br />local_settings.py所在位置</p>
</td>
</tr>
<tr>
<td>webapp/content</td>
<td>静态Web资源</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置Graphite-web数据库</span></div>
<p>你需要让Graphite-web的底层框架Django执行数据库的初始化。此数据库被用来存放用户设置、仪表盘，以及支持事件功能。</p>
<p>默认情况下，Graphite-web使用位于$STORAGE_DIR/graphite.db的SQLite数据库。如果要运行多个Graphite-web实例，则必须使用MySQL等数据库以便多个实例可以共享数据。 </p>
<div class="blog_h3"><span class="graybg">配置SQLite</span></div>
<p>执行下面的命令初始化SQLite数据库：</p>
<pre class="crayon-plain-tag">PYTHONPATH=/opt/graphite/webapp django-admin.py migrate --settings=graphite.settings --run-syncdb
# 完毕后 $STORAGE_DIR/graphite.db自动创建</pre>
<p>Django应用需要在Web服务器中运行，Web服务器需要对SQLite数据文件有读写权限。假设你使用Apache2，运行Apache2的用户为apache，则需要执行：</p>
<pre class="crayon-plain-tag">sudo chgrp graphite /opt/graphite/storage/graphite.db</pre>
<div class="blog_h2"><span class="graybg">配置WebApp</span></div>
<div class="blog_h3"><span class="graybg">httplocal_settings.py</span></div>
<p>Graphite不建议修改settings.py，所有定制化的配置，都应该在此文件中进行。常用的设置项如下表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">设置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>TIME_ZONE</td>
<td>时区，规范化的<a href="https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones">时区名称</a>。默认America/Chicago</td>
</tr>
<tr>
<td>DEBUG</td>
<td>是否启用Django错误页面。默认False</td>
</tr>
<tr>
<td>FLUSHRRDCACHED</td>
<td>如果设置，在从RRD文件获取数据前，执行<pre class="crayon-plain-tag">rrdtool flushcached</pre> <br />设置为rrdcached的地址或者Socket（例如unix:/var/run/rrdcached.sock）</td>
</tr>
<tr>
<td>MEMCACHE_HOSTS</td>
<td>如果设置，启用对计算出的目标、渲染过的图片的缓存。如果运行Graphite Web应用集群，则每个实例应当设置为一样的值<br />设置为memcached主机的数组，例如：['10.10.10.10:11211', '10.10.10.12:11211']</td>
</tr>
<tr>
<td>DEFAULT_CACHE_DURATION</td>
<td>数据、图片默认缓存时间。默认值60</td>
</tr>
<tr>
<td>DEFAULT_CACHE_POLICY</td>
<td>默认缓存策略。为元组的数组，每个元组指定最小查询时间、数据缓存时间<br />通过该设置，你可以让大的查询缓存更长时间。例如：<br />
<pre class="crayon-plain-tag">DEFAULT_CACHE_POLICY = [
    (0, 60), # 默认缓存60秒
    (7200, 120) # &gt;= 2小时以上时间范围的查询，缓存2分钟
] </pre>
</td>
</tr>
<tr>
<td>GRAPHITE_ROOT</td>
<td>Graphite的安装目录。默认/opt/graphite </td>
</tr>
<tr>
<td>CONF_DIR</td>
<td>额外Graphite-web配置文件目录。默认/opt/graphite/conf</td>
</tr>
<tr>
<td>STORAGE_DIR</td>
<td>存储目录。WHISPER_DIR、RRD_DIR、LOG_DIR、INDEX_FILE参照的基准目录</td>
</tr>
<tr>
<td>STATIC_ROOT</td>
<td>Graphite-web的静态文件目录。默认/opt/graphite/static<br />该目录一开始会不存在，你需要在设置STATIC_ROOT、STATIC_URL后执行：<br />
<pre class="crayon-plain-tag"># 如果报Unknown command: 'collectstatic' 说明INSTALLED_APPS中缺少
# django.contrib.staticfiles	
PYTHONPATH=/opt/graphite/webapp django-admin.py collectstatic 
    --noinput --settings=graphite.settings</pre></p>
<p>你还需要在Web服务器中把/static这个前缀映射到此目录，里Apache2为例：</p>
<pre class="crayon-plain-tag">Alias /static/ "/opt/graphite/static"</pre>
<p>如果你安装了whitenoise包，静态文件可以直接由Graphite webapp来处理，不通过Web服务器</p>
</td>
</tr>
<tr>
<td>DASHBOARD_CONF</td>
<td>仪表盘的配置文件。默认$CONF_DIR/dashboard.conf</td>
</tr>
<tr>
<td>GRAPHTEMPLATES_CONF</td>
<td>图形模板的配置文件。默认$CONF_DIR/graphTemplates.conf </td>
</tr>
<tr>
<td>WHISPER_DIR</td>
<td> Whisper数据文件目录。默认/opt/graphite/storage/whisper</td>
</tr>
<tr>
<td>RRD_DIR</td>
<td> RRD数据文件目录。默认/opt/graphite/storage/rrd</td>
</tr>
<tr>
<td>LOG_DIR</td>
<td> Graphite webapp的日志目录。默认$TORAGE_DIR/log/webapp</td>
</tr>
<tr>
<td>INDEX_FILE</td>
<td>搜索索引位置。默认/opt/graphite/storage/index<br />由build-index.sh脚本生成，运行Web应用的用户必须有写权限</td>
</tr>
<tr>
<td>URL_PREFIX</td>
<td>URL前缀</td>
</tr>
</tbody>
</table>
<p>我们的配置如下：</p>
<pre class="crayon-plain-tag"># 如果不设置，会导致报错：AttributeError: 'Settings' object has no attribute 'URL_PREFIX
URL_PREFIX = '/'</pre>
<div class="blog_h3"><span class="graybg">安装Apache和mod_wsgi</span></div>
<p>多种Web服务器可以用于运行基于Django的Web应用，这里我们选择Apache2。在CentOS下安装Apache2：</p>
<pre class="crayon-plain-tag">yum install httpd</pre>
<p>要让Apache2能够运行Python Web应用，需要安装模块mod_wsgi。参考<a href="https://blog.gmem.cc/django-study-note#mod-wsgi">Django学习笔记</a>完成mod_wsgi的构建与安装。</p>
<div class="blog_h3"><span class="graybg">配置Graphite虚拟主机</span></div>
<p>在/opt/graphite/examples/目录下，example-graphite-vhost.conf可以作为Apache虚拟主机的模板，复制该文件到/etc/httpd/conf.d/目录下，然后修改：</p>
<pre class="crayon-plain-tag">&lt;IfModule !wsgi_module.c&gt;
    LoadModule wsgi_module modules/mod_wsgi.so
&lt;/IfModule&gt;
WSGISocketPrefix run/wsgi
&lt;VirtualHost *:7767&gt;
        ServerName xcentos7.local
        DocumentRoot "/opt/graphite/webapp"
        ErrorLog /opt/graphite/storage/log/webapp/error.log
        CustomLog /opt/graphite/storage/log/webapp/access.log common
        WSGIDaemonProcess graphite processes=5 threads=5 display-name='%{GROUP}' inactivity-timeout=120
        WSGIProcessGroup graphite
        WSGIApplicationGroup %{GLOBAL}
        WSGIImportScript /opt/graphite/conf/graphite.wsgi process-group=graphite application-group=%{GLOBAL}
        WSGIScriptAlias / /opt/graphite/conf/graphite.wsgi
        Alias /content/ /opt/graphite/webapp/content/
        &lt;Directory /opt/graphite/webapp/content/&gt;
            Require all granted
        &lt;/Directory&gt;
        &lt;Directory /opt/graphite/conf/&gt;
            Require all granted
        &lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
<div class="blog_h3"><span class="graybg">验证配置</span></div>
<p>如果配置没有问题，启动Web服务器后访问：<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/render</pre> ，会显示一张330x250大小的图片，上面写着No Data。</p>
<div class="blog_h2"><span class="graybg">配置Carbon</span></div>
<p>所有Carbon守护进程可以基于多种通信协议，监听时间序列数据，并且对数据进行不同的处理。</p>
<p>Carbon的配置文件位于/opt/graphite/conf/目录，默认情况下没有预置的配置文件，但是Graphite提供了若干配置文件样例。你可以复制这些配置文件并定制：</p>
<pre class="crayon-plain-tag">pushd /opt/graphite/conf
cp carbon.conf.example carbon.conf
cp storage-schemas.conf.example storage-schemas.conf</pre>
<div class="blog_h3"><span class="graybg">carbon.conf</span></div>
<p>这是主配置文件，为每个Carbon守护进程定义配置项。该配置文件按段区分不同守护进程的配置：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 14%; text-align: center;">配置项段</td>
<td style="width: 30%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="17">[cache]   </td>
<td>
<p><span style="font-size: 8pt;">ENABLE_LOGROTATION</span></p>
</td>
<td>
<p> 是否启用每日的日志轮转，启用后每天创建一个日志</p>
</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USER </span></td>
<td>运行该进程的用户 </td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_CACHE_SIZE</span></td>
<td>内存中Metrics缓存的最大尺寸</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_UPDATES_PER_SECOND</span></td>
<td>每秒执行whisper的update_many()调用的最大次数，对应磁盘IO的次数，该配置项避免过度的磁盘使用</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_CREATES_PER_MINUTE</span></td>
<td>对每分钟最多创建的.wsp文件的个数进行软限制。超过限制的新Metrics对应的.wsp文件不会被创建，Metrics也被丢弃</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收文本格式数据的监听端口，默认0.0.0.0:2003</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_INTERFACE<br /></span></td>
<td rowspan="2">接收Pickle格式数据的监听端口，默认0.0.0.0:2004</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">ENABLE_UDP_LISTENER</span></td>
<td>是否启用UDP监听</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">UDP_RECEIVER_INTERFACE</span></td>
<td rowspan="2">通过UDP接收数据的监听端口，默认0.0.0.0:2003</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">UDP_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LOG_LISTENER_CONNECTIONS</span></td>
<td>对成功的连接请求记录日志</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">CACHE_QUERY_INTERFACE</span></td>
<td rowspan="2">缓存查询服务的监听端口，默认0.0.0.0：7002</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">CACHE_QUERY_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USE_FLOW_CONTROL</span></td>
<td>是否进行流量控制。如果设置为True，那么达到MAX_CACHE_SIZE后，会暂停接收数据，直到缓存占用小于MAX_CACHE_SIZE的95%</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">CACHE_WRITE_STRATEGY</span></td>
<td>按何种顺序将缓存从内存中移除，并写入到磁盘的策略：sorted、max、naive</td>
</tr>
<tr>
<td rowspan="13">[relay]</td>
<td><span style="font-size: 8pt;">LINE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">
<p>接收文本格式数据的监听端口，默认0.0.0.0:2003</p>
</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收Pickle格式数据的监听端口，默认0.0.0.0:2004</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LOG_LISTENER_CONNECTIONS</span></td>
<td>对成功的连接请求记录日志</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USER</span></td>
<td>运行该进程的用户</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">RELAY_METHOD</span></td>
<td>
<p>设置为rules：则该进程可以代替carbon-cache.py，然后中继所有Metrics给多个作为后端的carbon-cache.py</p>
<p>设置为consistent-hashing：则依据DESTINATIONS定义的分片策略，分发Metrics给多个作为后端的carbon-cache.py</p>
</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">REPLICATION_FACTOR</span></td>
<td>RELAY_METHOD=consistent-hashing时，可以指定N，从而把每个数据点分发到N台机器</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">DESTINATIONS</span></td>
<td>转发的目标，每个目标的格式是IP:PORT<br />RELAY_METHOD=rules时relay-rules.conf每个servers都要在此字段定义</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_DATAPOINTS_PER_MESSAGE</span></td>
<td>单个转发报文中包含的数据点的最大个数</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_QUEUE_SIZE</span></td>
<td>待转发的队列最大包含多少数据点</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">QUEUE_LOW_WATERMARK_PCT</span></td>
<td>队列低水位的百分比，0-1之间</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USE_FLOW_CONTROL</span></td>
<td>是否进行流量控制。如果设置为True，那么达到MAX_QUEUE_SIZE后，会暂停接收数据，直到转发队列低于QUEUE_LOW_WATERMARK_PCT</td>
</tr>
<tr>
<td rowspan="13">[aggregator]</td>
<td><span style="font-size: 8pt;"> LINE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收文本格式数据的监听端口，默认0.0.0.0:2023</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LINE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_INTERFACE</span></td>
<td rowspan="2">接收Pickle格式数据的监听端口，默认0.0.0.0:2004</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">PICKLE_RECEIVER_PORT</span></td>
</tr>
<tr>
<td><span style="font-size: 8pt;">LOG_LISTENER_CONNECTIONS</span></td>
<td>对成功的连接请求记录日志</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USER</span></td>
<td>运行该进程的用户</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">FORWARD_ALL</span></td>
<td>如果设置为True，除了根据aggregation-rules.conf进行聚合外，还把原始数据转发给DESTINATIONS</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">DESTINATIONS</span></td>
<td>聚合后的数据发送到的地方</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">REPLICATION_FACTOR</span></td>
<td>如果设置为N，则把数据转发给N个DESTINATION</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_QUEUE_SIZE</span></td>
<td>待转发的队列最大包含多少数据点</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">USE_FLOW_CONTROL</span></td>
<td>是否进行流量控制。如果设置为True，那么达到MAX_QUEUE_SIZE后，会暂停接收数据，直到转发队列低于80%</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_DATAPOINTS_PER_MESSAGE</span></td>
<td>单个转发报文中包含的数据点的最大个数</td>
</tr>
<tr>
<td><span style="font-size: 8pt;">MAX_AGGREGATION_INTERVALS</span></td>
<td>控制最多记住多少数据点，只有这些数据点才参与聚合<br />仅最近MAX_AGGREGATION_INTERVALS  * intervalSize秒内的数据点会被记住</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">storage-schemas.conf</span></div>
<p>这个配置文件用于定义Metrics的驻留率（ retention rates）——即Metrics的数据点（datapoints）以什么频率保存，保存多长时间。关于该配置文件，需要注意：</p>
<ol>
<li>该配置文件可以由多个段（Section）组成。每个段定义一个存储规则</li>
<li>这些规则按照从上到下的顺序对Metrics进行匹配，第一个匹配的规则对Metrics生效</li>
<li>匹配Metrics时，采用的是正则表达式</li>
<li>对于一个Metrics，其存储规则在接收到第一个数据时固化。因而修改此配置文件不会影响到既有的.wsp文件。要应用到既有的.wsp可以调用whisper-resize.py</li>
</ol>
<p>每个规则由三个部分组成：</p>
<pre class="crayon-plain-tag"># 段名称（规则名称），主要是文档用途。在匹配段的Metrics被创建时，日志creates.log中会显示此名称
[rulename]
# 匹配Metrics路径（Metrics的全限定名称，点号分隔）的正则式
# 举例：^servers\.www.*\.workers\.busyWorkers$
pattern=regex
# 驻留率表达式，数据点间隔:存留天数，时间后缀s,m,h,d,y分别表示秒、分、小时、天、年
# 举例：10s:14d 表示每个数据点表示10秒（相当于每10秒采集数据一次），并且存储14天的数据
retentions=retention rate</pre>
<p>注意，你可以指定多重驻留率表达式（retention rate），逗号分隔每个表达式。一般从最高精度:最短存留时间开始指定，直到最低精度:最长存留时间。例如：<pre class="crayon-plain-tag">15s:7d,1m:21d,15m:5y</pre> 表示7天内每15秒存留一个采样，而大于21天小于5年的则每15分钟一个采样。</p>
<p>设置多重驻留率表达式，可以在保存足够长时间的历史数据的前提下，尽量减少磁盘I/O和消耗的存储空间。当跨越驻留表达式的时间区间（上例中7天，21天）后，whisper会自动降低采样率（downsamples），<span style="background-color: #c0c0c0;">默认算法是取平均值</span>，可以通过storage-aggregation.conf定制聚合方式</p>
<div class="blog_h3"><span class="graybg">storage-aggregation.conf</span></div>
<p>这个配置文件定义如何在降低采样率（转换为低精度存储时） 如何对数据进行聚合。该文件的格式与storage-schemas.conf类似：</p>
<pre class="crayon-plain-tag">[rulename]
pattern = rexexp
# 0-1之间的浮点数，默认0.5。聚合区间内的值，至少多少比例为非空，聚合后的值才是非空
xFilesFactor = 0.5
# 数据聚合方式，默认average
aggregationMethod = average | sum | min | max | last</pre>
<p>同样的，修改此文件不会影响已经生成的.wsp文件， 要应用到既有的.wsp文件，可以调用whisper-set-aggregation-method.py。</p>
<div class="blog_h3"><span class="graybg">relay-rules.conf</span></div>
<p>这个配置文件指定中继规则——即需要把何种Metrics转发给何种后端。中继由Carbon的中继进程负责执行。该文件格式如下：</p>
<pre class="crayon-plain-tag">[rulename]
pattern = regex
servers = ip:port,ip:port,...</pre>
<div class="blog_h3"><span class="graybg">aggregation-rules.conf</span></div>
<p>该配置文件定义聚合出来的Metrics——由几个Metrics聚合而成的新的Metrics。聚合由Carbon的聚合进程负责执行。</p>
<p>注意：这里的聚合storage-aggregation.conf提及的聚合不同。后者用于单一Metrics的降低采样，而前者用于生成新的Metrics。</p>
<p>与其它配置文件不同，该文件一旦更改，立即生效。</p>
<p>该文件的格式如下：</p>
<pre class="crayon-plain-tag"># 捕获任何匹配input_pattern的Metrics，使用method聚合成新的Metrics：output_template
# frequency：每隔多久执行一次聚合
# method：可选sum/avg
output_template (frequency) = method input_pattern</pre>
<p>举例，假设你的Metrics的命名规则是：</p>
<pre class="crayon-plain-tag">&lt;env&gt;.applications.&lt;app&gt;.&lt;server&gt;.&lt;metric&gt;</pre>
<p>这是你可以配置如下聚合规则，来计算所有应用程序的请求的总数：</p>
<pre class="crayon-plain-tag">&lt;env&gt;.applications.&lt;app&gt;.all.requests (60) = sum &lt;env&gt;.applications.&lt;app&gt;.*.requests</pre>
<p>在此规则下，下面的Metrics：</p>
<pre class="crayon-plain-tag">test.applications.pems.221.request
test.applications.pems.6.request
test.applications.pems.5.request
test.applications.pems.201.request</pre>
<p>会每个60秒求和并生成 <pre class="crayon-plain-tag">test.applications.pems.all.request</pre> 的一个数据点。</p>
<div class="blog_h3"><span class="graybg">rewrite-rules.conf </span></div>
<p>该配置文件定义Metrics名称的改写规则。 与其它配置文件不同，该文件一旦更改，立即生效。该文件的格式如下：</p>
<pre class="crayon-plain-tag">[pre]
# pre段的规则，在接收到数据后立即执行改写
[post]
# post段的规则，在聚合完毕后执行改写
regex-pattern = replacement-text</pre>
<p> 举例：</p>
<pre class="crayon-plain-tag"># \1表示第一个捕获，即[a-z0-9]+
^collectd\.([a-z0-9]+)\. = \1.system.
# collectd.prod.cpu-0.idle-time 会被改写为 prod.system.cpu-0.idle-item</pre>
<div class="blog_h3"><span class="graybg">whitelist.conf和blacklist.conf </span></div>
<p>Carbon提供黑白名单功能。白名单：仅仅接受其中列出的Metrics；黑名单：拒绝其中列出Metrics。</p>
<p>要启用黑白名单功能，需要修改carbon.conf，设置<pre class="crayon-plain-tag">USE_WHITELIST = True</pre> </p>
<div class="blog_h2"><span class="graybg">启动Graphite</span></div>
<p>如何启动Graphite Webapp依赖于其运行的Web服务软件，以Apache为例：</p>
<pre class="crayon-plain-tag"># CentOS 7
systemctl restart httpd</pre>
<p>Carbon组件需要执行下面的命令来启动：</p>
<pre class="crayon-plain-tag">/opt/graphite/bin/carbon-cache.py start</pre>
<p>SQLite是一个“文件数据库”，不需要启动。如果Graphite Webapp使用其它数据库，例如MySQL，则需要启动之。 </p>
<div class="blog_h1"><span class="graybg">向Graphite发送数据点</span></div>
<div class="blog_h2"><span class="graybg">数据格式</span></div>
<p>你可以通过多种方式向Graphite（的Carbon组件）发送Metrics数据点。主要的三种数据格式是：Plaintext、Pickle、AMQP </p>
<div class="blog_h3"><span class="graybg">Plaintext协议</span></div>
<p>这种方式非常简单，你可以用如下格式来发送一条Metrics数据点：</p>
<pre class="crayon-plain-tag">&lt;metric path&gt; &lt;metric value&gt; &lt;metric timestamp&gt;
# 举例：
servers.zircon.cpu 85 1470303923</pre>
<p>出于测试目的，你可以直接通过命令向Carbon发送Metrics数据：</p>
<pre class="crayon-plain-tag">PORT=2003
SERVER=127.0.0.1
echo "servers.zircon.cpu 85 `date +%s`" | nc ${SERVER} ${PORT}</pre>
<div class="blog_h3"><span class="graybg">Pickle协议</span></div>
<p>Pickle是Python下的对象串行化框架，Pickle协议即它的串行化格式协议。使用该协议，你可以一次发送多个Metrics，并且串行化后的数据比较紧凑，因此Pickle协议性能更好。</p>
<p>下面是构建Pickle报文的示例代码：</p>
<pre class="crayon-plain-tag">listOfMetricTuples = [
    (path, (timestamp, value)),
    ...
]
payload = pickle.dumps(listOfMetricTuples, protocol=2)
header = struct.pack("!L", len(payload))
message = header + payload
# 然后通过Socket把message发送出去即可</pre>
<div class="blog_h2"><span class="graybg">客户端示例</span></div>
<div class="blog_h3"><span class="graybg">简单Pickle客户端</span></div>
<pre class="crayon-plain-tag">import pickle
import socket
import struct
import sys
import time
from time import sleep

import psutil


def get_cpu_load():
    load = psutil.cpu_percent()
    print load
    return load


if __name__ == '__main__':
    CARBON_HOST = '172.16.87.132'
    CARBON_PORT = 2004
    s = socket.socket()
    try:
        s.connect((CARBON_HOST, CARBON_PORT))
        while True:
            data = [
                ('servers.zircon.cpu', (time.time(), get_cpu_load()))
            ]
            pkg = pickle.dumps(data, 1)
            s.sendall(struct.pack('!L', len(pkg)))
            s.sendall(pkg)
            sleep(5)
    except socket.error:
        raise SystemExit("Failed to connect to %(host)s:%(port)" % {'host': CARBON_HOST, 'port': CARBON_PORT})
    except KeyboardInterrupt:
        sys.stderr.write("\nExiting on CTRL-C\n")
        sys.exit(0) </pre>
<div class="blog_h1"><span class="graybg">从Graphite获取数据</span></div>
<p>要从Graphite获取数据用于展示，可以使用Graphite Webapp暴露的Render URL API（RUA）。你可以通过</p>
<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/render</pre>
<p>访问此API。要向RUA传递参数，可以使用URL请求参数方式：<pre class="crayon-plain-tag">&amp;name=value</pre> 。注意大部分的参数名、函数名是大小写敏感的。</p>
<p>下面列出几个RUA的URL示例：</p>
<pre class="crayon-plain-tag"># Zircon的CPU负载图，获取800x600的图片
http://graphite/render?target=servers.zircon.cpu&amp;height=800&amp;width=600
# 最近12小时，所有服务器的CPU负载平均值
http://graphite/render?target=averageSeries(servers.*.load)&amp;from=-12hours
# 获取原始数据而不是图片，JSON格式
http://graphite/render?target=servers.zircon.cpu&amp;format=json</pre>
<div class="blog_h2"><span class="graybg">target参数</span></div>
<p>该参数用来指定从何处获取数据，你可以指定：</p>
<ol>
<li>单个Metrics路径</li>
<li>带有通配符的的Metrics路径，匹配多个Metrics</li>
<li>函数调用，针对作为入参的Metrics进行各种转换、合并操作</li>
</ol>
<div class="blog_h3"><span class="graybg">通配符</span></div>
<p>你可以在路径中使用三种风格的通配符：</p>
<ol>
<li><pre class="crayon-plain-tag">*</pre> 可以匹配0-N个字符，例如<pre class="crayon-plain-tag">servers.dev-*.cpu</pre> ，可以匹配所有开发服务器的CPU负载Metrics。</li>
<li><pre class="crayon-plain-tag">[...]</pre> 可以匹配列表中枚举的单个字符，例如<pre class="crayon-plain-tag">servers.dev-[a-z0-9].cpu</pre> ，可以匹配dev-0、dev-1等服务器的CPU负载Metrics。</li>
<li><pre class="crayon-plain-tag">{...}</pre> 可以匹配列表中枚举的单个字符串，例如<pre class="crayon-plain-tag">servers.{dev-0,dev-1}.cpu</pre> ，匹配dev-0、dev-2的CPU负载Metrics。</li>
</ol>
<p>注意：所有通配符都<span style="background-color: #c0c0c0;">不能跨越点号</span>。 </p>
<div class="blog_h3"><span class="graybg">template函数</span></div>
<p>你可以指定target为template函数调用，从而在Metrics路径中使用变量，例如：</p>
<pre class="crayon-plain-tag"># $varname 用来声明变量占位符
# template[varname]参数用来传递变量值
&amp;target=template(servers.$servername.cpu)&amp;template[servername]=zircon

# 可以使用数字代替变量名
&amp;target=template(servers.$1.cpu)&amp;template[1]=zircon

# template可以内嵌其它函数
&amp;target=template(constantLine($number))&amp;template[number]=123</pre>
<div class="blog_h3"><span class="graybg">所有函数</span></div>
<p>可用的函数较多，这里不一一列举说明，参见<a href="http://graphite.readthedocs.io/en/latest/functions.html">官方文档</a>。</p>
<div class="blog_h2"><span class="graybg">from/until参数</span></div>
<p>这两个可选参数用来指定相对或者绝对的时间区间（time period）。from表示区间起点，如果忽略，默认值是24小时之前；until表示区间终点，如果或略，默认值是当前时间点。</p>
<div class="blog_h3"><span class="graybg">相对时间</span></div>
<p>如果要使用相对时间，需要加上<pre class="crayon-plain-tag">-</pre> 前缀（负号），后面跟着数值和时间单位。时间单位包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">时间单位</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>s</td>
<td>秒</td>
</tr>
<tr>
<td>min</td>
<td>分钟</td>
</tr>
<tr>
<td>h</td>
<td>小时</td>
</tr>
<tr>
<td>d</td>
<td>天</td>
</tr>
<tr>
<td>w</td>
<td>周</td>
</tr>
<tr>
<td>mon</td>
<td>月（30天）</td>
</tr>
<tr>
<td>y</td>
<td>年（365天）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">绝对时间</span></div>
<p>你可以指定<pre class="crayon-plain-tag">HH:MM_YYYYMMDD</pre> 、<pre class="crayon-plain-tag">YYYYMMDD</pre> 、<pre class="crayon-plain-tag"> MM/DD/YY</pre> 等格式的时间绝对值。</p>
<div class="blog_h2"><span class="graybg">format参数</span></div>
<p>该参数用来指定要获取的数据的格式。 支持以下取值：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">格式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>png</td>
<td>根据指定的width、height，直接把数据渲染为PNG图片</td>
</tr>
<tr>
<td>raw</td>
<td>原始数据，分为多行，每行格式为：<br />
<pre class="crayon-plain-tag">&lt;target name&gt;,&lt;start timestamp&gt;,&lt;end timestamp&gt;,&lt;series step&gt;|[data]*</pre></p>
<p> 示例：</p>
<pre class="crayon-plain-tag">entries,1311836008,1311836013,1|1.0,2.0,3.0,5.0,6.0</pre>
</td>
</tr>
<tr>
<td>csv</td>
<td>基于逗号分隔符的格式，每行表示一个数据点。示例：<br />
<pre class="crayon-plain-tag">entries,2011-07-28 01:53:28,1.0
entries,2011-07-28 01:53:29,2.0
entries,2011-07-28 01:53:30,3.0
entries,2011-07-28 01:53:31,5.0
entries,2011-07-28 01:53:32,6.0</pre>
</td>
</tr>
<tr>
<td>json</td>
<td>
<p>JSON数组格式，示例：
<pre class="crayon-plain-tag">[{
  "target": "entries",
  "datapoints": [
    [1.0, 1311836008],
    [2.0, 1311836009],
    [3.0, 1311836010],
    [5.0, 1311836011],
    [6.0, 1311836012]
  ]
}]</pre>
<p>可以和jsonp参数联用，以便把数据包装成函数调用，进行跨域请求</p>
<p>可以和maxDataPoints参数联用，限定最大的数据点个数。超过数量的数据点将被压缩掉</p>
<p>可以和noNullPoints参数联用，移除所有Null值的数据点</p>
</td>
</tr>
<tr>
<td>svg</td>
<td>渲染为SVG图片格式</td>
</tr>
<tr>
<td>pdf</td>
<td>渲染为PDF文档</td>
</tr>
<tr>
<td>dygraph</td>
<td>dygraphs是一个快速、灵活的JavaScript图表（Chart）库。该格式返回dygraphs支持的数据格式。示例：<br />
<pre class="crayon-plain-tag">{
  "labels" : [
    "Time",
    "entries"
  ],
  "data" : [
    [1468791890000, 0.0],
    [1468791900000, 0.0]
  ]
}</pre>
</td>
</tr>
<tr>
<td>rickshaw</td>
<td>rickshaw是一个简单的JavaScript图表库。该格式返回rickshaw支持的数据格式。示例：<br />
<pre class="crayon-plain-tag">[{
  "target": "entries",
  "datapoints": [{
    "y": 0.0,
    "x": 1468791890
  }, {
    "y": 0.0,
    "x": 1468791900
  }]
}]</pre>
</td>
</tr>
<tr>
<td>pickle</td>
<td>返回Pickle串行化格式，设置MIME类型为application/pickle。反串行化后的对象示例：<br />
<pre class="crayon-plain-tag">[
  {
    'name' : 'summarize(test.data, "30min", "sum")',
    'start': 1335398400,
    'end'  : 1335425400,
    'step' : 1800,
    'values' : [None, None, 1.0, None],
  }
]</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">图形参数</span></div>
<p>你可以指定多个参数，来控制生成的图形的样式：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 22%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>areaAlpha</td>
<td>启用areaMode时，填充区域的透明度。0-1之间的浮点数</td>
</tr>
<tr>
<td>areaMode</td>
<td>填充曲线与X轴之间的区域，形成Area图，可以取值：<br />none 不进行填充<br />first 填充第一个目标<br />all  填充所有目标<br />stacked 堆叠模式，填充所有目标，一个目标的取值为前面所有其它目标的取值+该目标的取值</td>
</tr>
<tr>
<td>bgcolor</td>
<td>背景颜色。示例：<br />
<pre class="crayon-plain-tag"># 颜色名称
&amp;bgcolor=blue
# HEX代码
&amp;bgcolor=2222FF
# HEX代码，包含透明度
&amp;bgcolor=5522FF60</pre>
</td>
</tr>
<tr>
<td>cacheTimeout</td>
<td>被渲染出的图形，其有效缓存时间</td>
</tr>
<tr>
<td>colorList</td>
<td>多个Target时，每个Target的颜色，逗号分隔颜色代码</td>
</tr>
<tr>
<td>drawNullAsZero</td>
<td>是否把空值渲染为0</td>
</tr>
<tr>
<td>fontBold</td>
<td>是否使用粗体</td>
</tr>
<tr>
<td>fontItalic</td>
<td>是否使用斜体</td>
</tr>
<tr>
<td>fontName</td>
<td>字体名称，该字体必须安装在Graphite服务器上</td>
</tr>
<tr>
<td>fontSize</td>
<td>字体大小，大于1的浮点数</td>
</tr>
<tr>
<td>graphOnly</td>
<td>是否不显示网格线、X/Y轴和图例</td>
</tr>
<tr>
<td>graphType</td>
<td>图表类型，line或者pie</td>
</tr>
<tr>
<td>hideLegend</td>
<td>是否隐藏图例</td>
</tr>
<tr>
<td>hideAxes</td>
<td rowspan="3">是否隐藏X/Y轴</td>
</tr>
<tr>
<td>hideXAxis</td>
</tr>
<tr>
<td>hideYAxis</td>
</tr>
<tr>
<td>hideGrid</td>
<td>是否隐藏网格线</td>
</tr>
<tr>
<td>height</td>
<td rowspan="2">图形的高度、宽度，单位像素</td>
</tr>
<tr>
<td>width</td>
</tr>
<tr>
<td>leftColor</td>
<td>在双Y轴模式下，设置与左轴关联的Metrics的颜色</td>
</tr>
<tr>
<td>rightColor</td>
<td>在双Y轴模式下，设置与右轴关联的Metrics的颜色</td>
</tr>
<tr>
<td>leftDashed</td>
<td>在双Y轴模式下，是否以虚线绘制与左轴关联的Metrics</td>
</tr>
<tr>
<td>rightDashed</td>
<td>在双Y轴模式下，是否以虚线绘制与右轴关联的Metrics</td>
</tr>
<tr>
<td>leftWidth</td>
<td>在双Y轴模式下，设置与左轴关联的Metrics的线条宽度</td>
</tr>
<tr>
<td>rightWidth</td>
<td>在双Y轴模式下，设置与右轴关联的Metrics的线条宽度</td>
</tr>
<tr>
<td>lineMode</td>
<td>设置线条绘制的行为：<br />slope：从一个数据点向下一个数据点绘制斜线，Null值的区间不被绘制<br />connected：与slope类似，但是数据点总是被连接起来，无论它们之间是否存在Null值<br />staircase：绘制直方图</td>
</tr>
<tr>
<td>lineWidth</td>
<td>线条的宽度</td>
</tr>
<tr>
<td>majorGridLineColor</td>
<td>网格线主色</td>
</tr>
<tr>
<td>minorGridLineColor</td>
<td>网格线从色</td>
</tr>
<tr>
<td>minorY</td>
<td>每两个网格主线之间，有几个从线，Y方向</td>
</tr>
<tr>
<td>margin</td>
<td>图形四周的边距</td>
</tr>
<tr>
<td>maxDataPoints</td>
<td>使用</td>
</tr>
<tr>
<td>minXStep</td>
<td>
<p>两个连续的数据点之间，间隔的最小像素</p>
<p>如果数据点过多，则压缩，以满足此配置</p>
</td>
</tr>
<tr>
<td>noCache</td>
<td>禁止图片缓存</td>
</tr>
<tr>
<td>pieLabels</td>
<td>饼图标签如何显示，horizontal或者rotated</td>
</tr>
<tr>
<td>pieMode</td>
<td>饼图聚合方式：<br />average，取series中非空数据点的平均值<br />maximum，取series中非空数据点的最大值<br />minimum，取series中非空数据点的最小值</td>
</tr>
<tr>
<td>valueLabels</td>
<td>如何显示饼图分块的标签：<br />none，不显示<br />numbers，显示原始值<br />percent，显示百分比</td>
</tr>
<tr>
<td>valueLabelsColor</td>
<td>如何显示饼图分块的标签的颜色</td>
</tr>
<tr>
<td>valueLabelsMin</td>
<td>饼图中，分块占比小于此数值的分块，不显示其标签</td>
</tr>
<tr>
<td>title</td>
<td>在图形顶端显示的标题</td>
</tr>
<tr>
<td>vtitle</td>
<td>Y轴标题，垂直显示</td>
</tr>
<tr>
<td>vtitleRight</td>
<td>双Y轴模式下，右Y轴的标题</td>
</tr>
<tr>
<td>tz</td>
<td>用于显示时间值的时区</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">展示Graphite数据</span></div>
<p>上一章内容我们讨论了如何获取Graphite数据。通过Render URL API，我们不但可以获得文本数据，还可以直接获得渲染好的图片。这意味着通过Render URL API本身就可以实现Metrics的渲染，你只需要把生成的图片嵌入到自己的应用程序中即可。</p>
<p>Graphite Webapp本身提供了基于ExtJS的一个管理界面，你可以通过<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/admin</pre> 浏览Metrics。</p>
<p>Graphite生成的Metrics曲线的图片，不是非常美观，而静态图片也缺乏交互性。因此，实际项目中常常结合使用第三方基于JavaScript的Charts库来做展示，例如：</p>
<ol>
<li><a href="http://grafana.org/">Grafana</a>：UI比较绚丽，支持设计仪表盘、时间区间联动。参见：<a href="/time-series-data-renderering-with-grafana">使用Grafana展示时间序列数据</a></li>
<li><a href="http://jondot.github.io/graphene/">Graphene</a>：一个较为简单的，基于D3.js和Backbone.js的Graphite仪表盘工具</li>
</ol>
<div class="blog_h1"><span class="graybg">Graphite事件</span></div>
<p>除了简单的，基于Key/Value的Metrics数据，Graphite还可以存储、展示随机出现的数据——事件。</p>
<p>事件不适合存储在Whisper中，因此它被存储在Graphite的Webapp的数据库中（默认使用SQLite）。</p>
<div class="blog_h2"><span class="graybg">发布事件</span></div>
<p>通过向<pre class="crayon-plain-tag">http://GRAPHITE_HOST:GRAPHITE_PORT/events/</pre> 发送POST请求，即可发布Graphite事件。事件使用JSON格式编码在请求体中：</p>
<pre class="crayon-plain-tag">{ 
    "what": "事件类型", 
    "tags": "标签",
    "data": "事件相关的数据" 
}</pre>
<div class="blog_h2"><span class="graybg">查询事件</span></div>
<p>指定target为<pre class="crayon-plain-tag">event(*tags)</pre> 函数调用，即可通过<pre class="crayon-plain-tag">/render</pre> 查询事件，例如</p>
<pre class="crayon-plain-tag">[
   {
      "target" : "events(mytag)",
      "datapoints" : [
         [
            1,
            1388966651
         ],
         [
            3,
            1388966652
         ]
      ]
   }
]</pre>
<p>你也可以通过<pre class="crayon-plain-tag">/render/events/get_data</pre> 获得原始的事件数据，例如：</p>
<pre class="crayon-plain-tag">[
   {
      "when" : 1392046352,
      "tags" : "mytag",
      "data" : "...",
      "id" : 2,
      "what" : "Event - deploy"
   }
] </pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">AttributeError: 'WSGIRequest' object has no attribute 'REQUEST'</span></div>
<p>Django的request对象曾经有一个属性REQUEST，用来获得通过GET或者POST请求传递的请求参数，在1.9版本中此属性已经移除。</p>
<p>Graphite代码没有即时更新，存在不兼容的问题，修改一下即可：</p>
<pre class="crayon-plain-tag">def parseOptions(request):
   queryParams = request.GET # request.REQUEST已经被移除</pre>
<p>还有很多其它views.py存在同样的问题，最好搜索一下一并修改。如果觉得麻烦可以安装兼容版本的Django：</p>
<pre class="crayon-plain-tag">pip install django==1.8.14</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/graphite-study-note">Graphite学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/graphite-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Django学习笔记</title>
		<link>https://blog.gmem.cc/django-study-note</link>
		<comments>https://blog.gmem.cc/django-study-note#comments</comments>
		<pubDate>Fri, 13 Feb 2015 08:45:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=12290</guid>
		<description><![CDATA[<p>简介 Django是一个Python的Web开发框架，它是一个WSGI规范的框架端实现。使用该框架可以把你从Web开发的很多重复劳动中解放出来而专注于业务逻辑。 Django运行速度非常快、并且是可扩容的，安全性方面也做的很好，可以避免一些常见的安全性错误。 安装 安装Python 最新的1.9版本，要求Python版本为2.7、3.4或者3.5。具体安装步骤本文不赘述，可以参考Python知识集锦。 安装Web服务器 Django内置了一个简单的开发用Web服务器，可以用于开发和测试，要启动此服务器，只需要： [crayon-69de53fb59e99244738004/] 这个开发服务器会自动重新载入最新的Python代码，便于开发调试。 在生产环境下你应当使用可靠的Web服务器，例如Apache、Nginx等。 Apache + mod_wsgi 这个组合可以作为Django的Web容器。Apache是广泛使用的、模块化的Web服务器；而mod_wsgi模块则是一个Apache模块，同时也是WSGI规范的服务器端实现，安装此模块后Apache可以和任何支持WSGI的Python Web应用通信，例如基于Django的应用。 mod_wsgi有两种运行模式： 嵌入式模式：类似于mod_perl，Python被嵌入在Apache中，Python代码在Apache启动时加载到Apache进程空间并驻留直到Apache进程结束。这种方式具有较高的性能 守护模式：mod_wsgi产生独立进程，由该进程来处理请求。此进程的运行用户可以不同于Apache进程。此进程可以独立于Apache重启，因而在开发期间可以更无缝的更新代码 手工构建mod_wsgi的步骤如下： [crayon-69de53fb59e9d098345999/] 然后，你需要让Apache加载mod_wsgi模块。你可以使用a2enmod命令或者修改配置文件：  <a class="read-more" href="https://blog.gmem.cc/django-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/django-study-note">Django学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Django是一个Python的Web开发框架，它是一个WSGI规范的<span style="background-color: #c0c0c0;">框架端实现</span>。使用该框架可以把你从Web开发的很多重复劳动中解放出来而专注于业务逻辑。</p>
<p>Django运行速度非常快、并且是可扩容的，安全性方面也做的很好，可以避免一些常见的安全性错误。</p>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">安装Python</span></div>
<p>最新的1.9版本，要求Python版本为2.7、3.4或者3.5。具体安装步骤本文不赘述，可以参考<a href="/python-faq">Python知识集锦</a>。</p>
<div class="blog_h2"><span class="graybg">安装Web服务器</span></div>
<p>Django内置了一个简单的开发用Web服务器，可以用于开发和测试，要启动此服务器，只需要：</p>
<pre class="crayon-plain-tag"># 先进入工程根目录
cd librarymgr
# 启动开发服务器
python manage.py runserver</pre>
<p>这个开发服务器会<span style="background-color: #c0c0c0;">自动重新载入最新的Python代码</span>，便于开发调试。</p>
<p>在生产环境下你应当使用可靠的Web服务器，例如Apache、Nginx等。</p>
<div class="blog_h3"><span class="graybg"><a id="mod-wsgi"></a>Apache + mod_wsgi</span></div>
<p>这个组合可以作为Django的Web容器。Apache是广泛使用的、模块化的Web服务器；而mod_wsgi模块则是一个Apache模块，同时也是WSGI规范的<span style="background-color: #c0c0c0;">服务器端实现</span>，安装此模块后Apache可以和任何支持WSGI的Python Web应用通信，例如基于Django的应用。</p>
<p>mod_wsgi有两种运行模式：</p>
<ol>
<li>嵌入式模式：类似于mod_perl，Python被嵌入在Apache中，Python代码在Apache启动时加载到Apache进程空间并驻留直到Apache进程结束。这种方式具有较高的性能</li>
<li>守护模式：mod_wsgi产生独立进程，由该进程来处理请求。此进程的运行用户可以不同于Apache进程。此进程可以独立于Apache重启，因而在开发期间可以更无缝的更新代码</li>
</ol>
<p>手工构建mod_wsgi的步骤如下：</p>
<pre class="crayon-plain-tag">wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.3.tar.gz
tar xzf 4.5.3.tar.gz 
cd mod_wsgi-4.5.3/
./configure
# 在Linux上，Apache2默认使用的MPM（多处理模块）是prefork，下面安装开发支持
# 如果是CentOS，安装 yum install httpd-devel
sudo apt-get install apache2-prefork-dev
make &amp;&amp; sudo make install 
# 构建好的模块默认被安装到：
# Ubuntu 14 /usr/lib/apache2/modules/mod_wsgi.so
# CentOS 7  /usr/lib64/httpd/modules/mod_wsgi.so</pre>
<p>然后，你需要让Apache加载mod_wsgi模块。你可以使用<a href="/apache-http-server-faq#a2enmod-usage">a2enmod</a>命令或者修改配置文件： </p>
<pre class="crayon-plain-tag">LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so</pre>
<div class="blog_h2"><span class="graybg">配置数据库</span></div>
<p>如果要使用Django的数据库API，你需要安装数据库服务器。Django支持多种数据库后端，官方支持包括：PostgreSQL、MySQL、Oracle和SQLite，其它主流数据库有<a href="https://docs.djangoproject.com/en/1.9/ref/databases/#third-party-notes">第三方支持</a>。开发一个简单的项目，不需要在生成环境部署时，你可以使用SQLite，它不需要独立的服务器，也不需要安装第三方Python模块。</p>
<p>除了后端以外，你还需要安装对应的Python database bindings：</p>
<pre class="crayon-plain-tag"># MySQL
sudo pip install mysqlclient
# PostgreSQL
sudo pip install  psycopg2</pre>
<p>如果你想用Django的<pre class="crayon-plain-tag">manage.py migrate</pre> 命令为工程模型自动创建表结构，你需要目标数据库用户授予对应的权限。</p>
<div class="blog_h2"><span class="graybg">安装Django</span></div>
<p>你可以通过pip安装：</p>
<pre class="crayon-plain-tag"># 安装Release
sudo pip install django
# 安装开发版本
git clone git://github.com/django/django.git
sudo pip install -e django/</pre>
<div class="blog_h1"><span class="graybg">Hello World</span></div>
<p>本章通过一个简单的图书馆管理系统，来了解Django的开发流程和基本的API。</p>
<div class="blog_h2"><span class="graybg">创建新工程</span></div>
<p>开始编程之前，你需要创建一个Django工程：</p>
<pre class="crayon-plain-tag">cd ~/Python/projects/pycharm
django-admin startproject librarymgr</pre>
<p>上面的命令会在当前目录下创建一个librarymgr目录，这是<span style="background-color: #c0c0c0;">新工程的根目录</span>，其结构如下：</p>
<pre class="crayon-plain-tag">└── librarymgr               # 工程容器目录
    ├── manage.py            # 一个命令行工具，通过它你可以和Django工程互动
    └── librarymgr           # 当前工程对应的Python包
        ├── __init__.py      # 空白文件，仅仅用于识别当前目录为一个包
        ├── settings.py      # 当前Django工程的设置/配置
        ├── urls.py          # 当前工程的URL映射声明
        └── wsgi.py          # WSGI兼容的Web服务器的入口点，部署工程时用到此文件</pre>
<p>一个Django实例的全部设置：  数据库配置、Django选项、应用程序选项，都存放在工程目录下。</p>
<div class="blog_h2"><span class="graybg">导入工程到PyCharm</span></div>
<p>你可以直接把工程容器目录作为PyCharm工程打开，PyCharm会自动识别并启用Django支持。</p>
<div class="blog_h2"><span class="graybg">启动开发服务器</span></div>
<p>执行下面的命令，可以启动开发服务器：</p>
<pre class="crayon-plain-tag"># 默认监听127.0.0.1:8000
python manage.py runserver   
# 监听所有网络接口
python manage.py runserver 0.0.0.0:8000</pre>
<p>注意此服务器会自动Reload最新的Python代码，因而你不需要在修改代码后重启服务器。但是某些时候，例如<span style="background-color: #c0c0c0;">添加了新文件，可能不会自动载入</span>，这时你需要手工重启。 </p>
<p>使用PyCharm时，用于启动开发服务器的Run Configuration会自动创建，点击工具栏按钮<img class="aligncenter size-full wp-image-12323 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2015/02/Selection_003.png" alt="Selection_003" width="132" height="21" />即可启动。 </p>
<div class="blog_h2"><span class="graybg">图书管理模块V1</span></div>
<p>到目前为止，开发环境已经搭建完毕，可以实现功能了。</p>
<p>在一个工程中，可以包含多个<span style="background-color: #c0c0c0;">Django应用，有时你可能觉得称之为模块更合适</span>。而<span style="background-color: #c0c0c0;">每个应用也可以归属于多个工程</span>。工程中的应用共享工程的配置——例如数据库连接。每个Django应用由一个Python包组成，这个包包含一些约定俗成的内容。</p>
<p>你可以把Django应用放置在PYTHONPATH中的任意目录，在这里，我们把它们放置在工程根目录下。执行下面的命令，创建一个应用：</p>
<pre class="crayon-plain-tag">python manage.py startapp bookmgr</pre>
<p>命令执行完毕后，工程结构如下：</p>
<pre class="crayon-plain-tag">├── bookmgr
│   ├── admin.py
│   ├── apps.py                 # 包含当前应用的定义及配置
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py               # 在这里定义模型类
│   ├── tests.py
|   ├── urls.py                 # 在这里定义URLconf，即URL和视图的映射关系
│   └── views.py                # 在这里创建新的视图
├── librarymgr
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── settings.py
│   ├── settings.pyc
│   ├── urls.py
│   └── wsgi.py
└── manage.py</pre>
<div class="blog_h3"><span class="graybg">开发第一个视图</span></div>
<p>我们为图书管理模块添加一个最简单的视图，在响应中简单的输出欢迎文字：</p>
<pre class="crayon-plain-tag">from django.http.response import HttpResponse

def index(request):
    return HttpResponse("Welcome to Book Management module.") </pre>
<p>要调用视图，必须将其映射到某个URL，因而我们需要<pre class="crayon-plain-tag">URLconf</pre> ，要为bookmgr创建URLconf，只需要新建一个<pre class="crayon-plain-tag">urls.py</pre> 文件：</p>
<pre class="crayon-plain-tag">from django.conf.urls import url

from . import views

# 该固定名称的变量定义一系列的URL映射规则
urlpatterns = [
    # url函数定义URL到视图的映射，注意URL的base部分可能已经被父URLconf脱去。该函数接受4个参数
    # regex 匹配URL的正则式，注意此正则式不搜索GET/POST参数或者网址的域名部分，例如对于
    #       https://librarymgr.gmem.cc/bookmgr/?id=1，进行匹配判断的仅仅时bookmgr/
    # view  匹配后，需要调用的视图函数，并把HttpRequest作为第一个入参，正则式中捕获的分组依次作为后续位置参数
    #       如果使用正则式的命名分组，则捕获的分组作为关键字参数传入
    # kwargs 所谓关键字参数
    # name  为URL命名，以便从Django的其它地方——例如模板中——无歧义的引用之
    url(r'^$', views.index, name = 'index')
]</pre>
<p>完成URL到视图的映射后，必须把URLconf<span style="background-color: #c0c0c0;">包含到工程的根URLconf</span>中去： </p>
<pre class="crayon-plain-tag">from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 使用include函数引用其它URLconf
    # 在处理请求时，前缀匹配部分bookmgr/会自动去除，把剩余的部分传递给子URLconf，进行匹配
    url(r'^bookmgr/', include('bookmgr.urls'))
]</pre>
<p>现在启动开发服务器，输入网址http://127.0.0.1:8000/bookmgr/，你应该可以看到之前编写的欢迎文字。 </p>
<div class="blog_h2"><span class="graybg">配置数据库</span></div>
<p>传统的增删改查应用是离不开数据库的，我们可以为工程中所有模块配置共享的数据源。</p>
<p><pre class="crayon-plain-tag">librarymgr/settings.py</pre> 是一个<span style="background-color: #c0c0c0;">用作配置文件的Python模块</span>，我们可以在其中配置很多代表了Django设置的模块变量，例如数据库连接信息。</p>
<p>默认情况下，配置文件包含了SQLite的配置：</p>
<pre class="crayon-plain-tag"># 工程使用的所有数据源
DATABASES = {
    # 单个数据源
    'default': {
        # 引擎（数据库后端），其它可选引擎包括：
        # django.db.backends.postgresql、django.db.backends.mysql、django.db.backends.oracle等
        'ENGINE': 'django.db.backends.sqlite3',
        # 数据库的名称，如果使用SQLite，则指定数据库文件的绝对路径
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}</pre>
<p>不使用SQLite时，你需要提供额外的配置项。本章使用MySQL数据库：</p>
<pre class="crayon-plain-tag">DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}</pre>
<div class="blog_h3"><span class="graybg">初始化数据库</span></div>
<p>由于默认激活的那些应用程序必须使用表，因此必须初始化数据库，即<span style="background-color: #c0c0c0;">创建表结构、初始化表数据</span>，执行下面的命令：</p>
<pre class="crayon-plain-tag"># 根据DATABASES设置，以及应用的INSTALLED_APPS的database migrations，完成表创建和数据插入
python manage.py migrate</pre>
<div class="blog_h2"><span class="graybg">settings.py中的其它设置项</span></div>
<div class="blog_h3"><span class="graybg">TIME_ZONE</span></div>
<p>配置完数据库后，顺便设置一下时区：</p>
<pre class="crayon-plain-tag">TIME_ZONE = 'Asia/Shanghai'</pre>
<div class="blog_h3"><span class="graybg">INSTALLED_APPS</span></div>
<p>包含该实例激活的Django应用程序的名称，默认激活以下通用应用：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">应用程序</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>django.contrib.admin</td>
<td>管理站点（Admin site）</td>
</tr>
<tr>
<td>django.contrib.auth</td>
<td>身份验证系统</td>
</tr>
<tr>
<td>django.contrib.contenttypes</td>
<td>内容类型框架</td>
</tr>
<tr>
<td>django.contrib.sessions</td>
<td>会话支持</td>
</tr>
<tr>
<td>django.contrib.messages</td>
<td>一个消息框架</td>
</tr>
<tr>
<td>django.contrib.staticfiles</td>
<td>处理静态文件的框架</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">图书管理模块V2</span></div>
<div class="blog_h3"><span class="graybg">添加模型类</span></div>
<p>图书管理模块涉及的模型包括书籍类目、书籍、书籍实例等，我们需要将它们建模为Python类。</p>
<p>每个模型类都应该是django.db.models.Model的子类型，这是Django提供的充血模型基类。我们创建以下三个类：</p>
<pre class="crayon-plain-tag">from django.db import models

from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible  # 如果需要支持Python2
class Category(models.Model):
    # 每个模型会定义若干个Field子类型的变量，这些变量对应数据库的字段，二者默认名字一致
    # 字符串类型的字段
    # 第一个可选参数是人类友好的字段名称
    name = models.CharField('Category name', max_length = 128)
    # 对其它模型的引用，自引用必须用引号包围的类名或者'self'
    # Django支持o2m、m2o、m2m等常见数据库关系
    # blank=True表示表单验证时允许为空，null=True表示数据库字段可以为空
    parent_category = models.ForeignKey('self', blank=True, null=True )
    # 下面这个函数定义模型的字符串展示
    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length = 128)
    category = models.ForeignKey(Category)
    isbn = models.BigIntegerField()
    pub_date = models.DateTimeField()


class BookInstance(models.Model):
    no = models.IntegerField()
    book = models.ForeignKey(Book)
    # 枚举值和枚举字段
    STATUS_LEND_OUT = 1
    STATUS_RETURNED = 0
    status = models.SmallIntegerField(
        choices = ((STATUS_LEND_OUT, 'Lend out'), (STATUS_RETURNED, 'Returned')),
        # 字段可以提供默认值
        default = STATUS_RETURNED
    )</pre>
<div class="blog_h3"><span class="graybg">激活模型并生成迁移文件</span></div>
<p>Django根据上面一小片模型代码，可以自动生成Schema（表结构）。<span style="background-color: #c0c0c0;">首先，你需要安装bookmgr这个应用</span>：</p>
<pre class="crayon-plain-tag">INSTALLED_APPS = [
    # 在开始处添加下面的内容，BookmgrConfig类以及自动在apps.py中定义
    'bookmgr.apps.BookmgrConfig'
]</pre>
<p>然后执行下面的命令，生成一个<span style="background-color: #c0c0c0;">“迁移”</span>：</p>
<pre class="crayon-plain-tag"># 运行makemigrations命令，告知Django你对模型执行了修改（这里是添加了新模型）
# 并且你想把这个改变保存到一个迁移（migration）中
python manage.py makemigrations bookmgr
# 输出如下：
# Migrations for 'bookmgr':
#   0001_initial.py:
#     - Create model Book
#     - Create model BookInstance
#     - Create model Category
#     - Add field category to book</pre>
<p>所谓迁移，只是磁盘上的文件，Django用它来记录模型（以及对应的数据库Schema）的变化。</p>
<p>你可以到应用的migrations目录查看新生成的迁移0001_initial.py，它同样是一段标准的Python代码。</p>
<div class="blog_h3"><span class="graybg">执行数据库迁移</span></div>
<p>使用命令<pre class="crayon-plain-tag">python manage.py sqlmigrate bookmgr 0001</pre> ，可以将上面的0001号<span style="background-color: #c0c0c0;">迁移文件转换为SQL脚本</span>：</p>
<pre class="crayon-plain-tag">BEGIN;
--
-- Create model Book
--
CREATE TABLE `bookmgr_book` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(128) NOT NULL,
    `isbn` bigint NOT NULL, `pub_date` datetime NOT NULL
);
--
-- Create model BookInstance
--
CREATE TABLE `bookmgr_bookinstance` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `no` integer NOT NULL, 
    `status` smallint NOT NULL, `book_id` integer NOT NULL)
;
--
-- Create model Category
--
CREATE TABLE `bookmgr_category` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, 
    `name` varchar(128) NOT NULL, `parent_category_id` integer NOT NULL
);
--
-- Add field category to book
--
ALTER TABLE `bookmgr_book` ADD COLUMN `category_id` integer NOT NULL;
ALTER TABLE `bookmgr_book` ALTER COLUMN `category_id` DROP DEFAULT;
ALTER TABLE `bookmgr_bookinstance` ADD CONSTRAINT `bookmgr_bookinstance_book_id_73e96047_fk_bookmgr_book_id` 
    FOREIGN KEY (`book_id`) REFERENCES `bookmgr_book` (`id`);
ALTER TABLE `bookmgr_category` ADD CONSTRAINT `bookmgr_categ_parent_category_id_736b2e01_fk_bookmgr_category_id` 
    FOREIGN KEY (`parent_category_id`) REFERENCES `bookmgr_category` (`id`);
CREATE INDEX `bookmgr_book_b583a629` ON `bookmgr_book` (`category_id`);
ALTER TABLE `bookmgr_book` ADD CONSTRAINT `bookmgr_book_category_id_e3c50876_fk_bookmgr_category_id` 
    FOREIGN KEY (`category_id`) REFERENCES `bookmgr_category` (`id`);

COMMIT;</pre>
<p>关于上述脚本，你需要了解：</p>
<ol>
<li>我们没有定义主键字段id，但是父类Model定义了，因此继承而来。你也可以覆盖默认定义</li>
<li>主键的生成规则依据数据库的不同，自动设置</li>
<li>表名称默认为：应用名称_模型类名，模型类名自动转为小写</li>
<li>外键字段的名称为：引用字段名称_id</li>
</ol>
<p>sqlmigrate命令只是打印SQL，并不会执行数据库迁移。后者通过下面的命令完成：</p>
<pre class="crayon-plain-tag"># 把所有尚未执行的迁移应用到数据库中
python manage.py migrate</pre>
<p>注意，你可以在执行迁移之前执行check命令，确保工程中没有错误：</p>
<pre class="crayon-plain-tag">python manage.py check</pre>
<div class="blog_h2"><span class="graybg">关于迁移功能</span></div>
<p>Django的迁移功能很强大，它在django_migrations表中记录数据库的当前状态，因而<span style="background-color: #c0c0c0;">可以跟踪迁移的进度</span>。每次执行migrate命令时，Django总是把尚未执行的迁移应用到数据库中。迁移功能还能在升级数据库的同时<span style="background-color: #c0c0c0;">避免数据丢失</span>。</p>
<p>当你修改模型后，记住以下执行三个步骤：</p>
<ol>
<li>在models.py中修改模型类</li>
<li>创建迁移文件：<pre class="crayon-plain-tag">python manage.py makemigrations</pre> </li>
<li>执行数据库迁移：<pre class="crayon-plain-tag">python manage.py migrate</pre> </li>
</ol>
<div class="blog_h2"><span class="graybg">通过API操控模型</span></div>
<p>本节我们尝试通过交互式的Python控制台来试验Django的API，首先执行下面的命令启动控制台：</p>
<pre class="crayon-plain-tag">python manage.py shell</pre>
<p>现在可以可以编写代码试验功能了：</p>
<pre class="crayon-plain-tag"># 导入模型类
from bookmgr.models import Category, Book, BookInstance
# 查询所有类目
Category.objects.all()
# 输出：[]  因为当前是空表
# 新建一个类目，你可以使用关键字参数传递各字段的值，这是Model类的行为
c = Category(name='social science')
# 必须调用save()，才能持久化到数据库中
c.save()
# 根据主键get，亦可入参id=1
c_sc = Category.objects.get(pk=1)
# 外键字段赋值，使用模型对象，而不是其ID
c = Category(name='economics',parent_category=c_sc)
c.save()
c = Category(name='politics',parent_category=c_sc)
c.save()
# 自动产生的双向关联，使用对方模型名称_set访问
c_sc.category_set.all()    # [&lt;Category: Category object&gt;, &lt;Category: Category object&gt;]
# 通过双向关联创建新对象
c_sc.category_set.create(name='law')
c_sc.category_set.create(name='pedagogy')</pre>
<div class="blog_h2"><span class="graybg">通过Django Admin管理模型 </span></div>
<p>执行下面的命令，来创建超级用户</p>
<pre class="crayon-plain-tag">python manage.py createsuperuser</pre>
<p>现在通过http://127.0.0.1:8000/admin登陆，默认情况下你可以管理用户和组。</p>
<p>要允许Admin应用管理模型对象，你需要进行注册：</p>
<pre class="crayon-plain-tag">from django.contrib import admin

from .models import Category, Book, BookInstance

admin.site.register(Category)
admin.site.register(Book)
admin.site.register(BookInstance)</pre>
<p>重启服务后，你就可以在Web界面中添加、修改、删除模型了。 依据模型字段类型的不同，Admin应用会呈现不同的表单元素。</p>
<div class="blog_h2"><span class="graybg">图书管理模块V3</span></div>
<p>本节我们开发几个视图，用来展示Book模型。 </p>
<p>为了可维护性的考虑，不应当把HTML片段硬编码在视图函数中，我们可以使用Django提供的模板子系统来分离Python代码和HTML代码。依据Django的默认设置：</p>
<pre class="crayon-plain-tag">TEMPLATES = [
    {
        # 使用何种模板引擎
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 在应用的根目录下寻找templates目录  
        'APP_DIRS': True,
    },
]</pre>
<p>你需要在应用（bookmgr）的根目录下创建一个<pre class="crayon-plain-tag">templates</pre>子目录，Django会在此目录中寻找需要的模板。</p>
<div class="blog_h3"><span class="graybg">静态文件</span></div>
<p>任何网站都需要图片、脚本、样式表等静态资源。在Django框架中应用<pre class="crayon-plain-tag">django.contrib.staticfiles</pre> 专门负责处理静态文件，此应用默认已经加载。</p>
<p>你需要设置静态文件的URL前缀以及静态文件搜索目录：</p>
<pre class="crayon-plain-tag"># URL前缀
STATIC_URL = '/static/'
# 静态文件搜索目录，这里工程根目录下的static目录
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)</pre>
<p>在Django模板中，你可以类似<pre class="crayon-plain-tag">/static/css/default.css</pre> 这样的硬编码来引用静态文件，或者使用static这个模板标签（tag）：</p>
<pre class="crayon-plain-tag">{% load static from staticfiles %}
&lt;link rel="stylesheet" href="{% static "css/default.css" %}"&gt;</pre>
<p>要能够正常访问default.css，你必须将其存放到<pre class="crayon-plain-tag">librarymgr/static/css/default.css</pre>  </p>
<div class="blog_h3"><span class="graybg">Book首页</span></div>
<p>现在我们编写Book模块的首页，它展示欢迎信息和最新出版的10本图书。视图函数如下：</p>
<pre class="crayon-plain-tag">def book_index(request):
    # 上下文对象
    context = {
        'books': Book.objects.order_by('-pub_date')[:10]
    }

    # 加载模板
    template = loader.get_template('book/index.html')
    # 调用模板的render()方法，可以把上下文变量合并入模板，并生成字符串
    return HttpResponse(template.render(context, request))

    # 亦可使用下面的捷径：
    from django.shortcuts import render
    return render(request, 'book/index.html', context)</pre>
<p>URL映射定义如下：</p>
<pre class="crayon-plain-tag">url(r'^book/index', views.book_index, name = 'book_index')</pre>
<p>模板代码如下：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Book Index&lt;/title&gt;
    {% load static from staticfiles %}
    &lt;link rel="stylesheet" href="{% static 'css/default.css' %}"&gt;
&lt;/head&gt;
&lt;body&gt;

{% if books %}
    &lt;div&gt;&lt;span&gt;Recently 10 published books:&lt;/span&gt;&lt;/div&gt;
    &lt;table&gt;
        &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;Book name&lt;/th&gt;
            &lt;th&gt;Publish date&lt;/th&gt;
            &lt;th&gt;Action&lt;/th&gt;
        &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
        {% for book in books %}
            &lt;tr&gt;
                &lt;td&gt;&lt;a href="{% url 'book_view' book.id %}"&gt;{{ book.name }}&lt;/a&gt;&lt;/td&gt;
                &lt;td&gt;{{ book.pub_date }}&lt;/td&gt;
                &lt;td&gt;&lt;a href="{% url 'book_edit' book.id %}"&gt;&lt;img src="{% static 'images/edit.png' %}"/&gt;&lt;/a&gt;&lt;/td&gt;
            &lt;/tr&gt;
        {% endfor %}
        &lt;/tbody&gt;
    &lt;/table&gt;
{% else %}
    No book available.
{% endif %}
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>这里可以大概了解一下Django模板的语法风格：</p>
<ol>
<li>使用<pre class="crayon-plain-tag">{% %}</pre> 语法声明流程控制指令</li>
<li>使用<pre class="crayon-plain-tag">{{ }}</pre> 语法声明占位符</li>
<li>使用点号导航语法来访问对象属性</li>
</ol>
<p>打开：http://127.0.0.1:8000/bookmgr/book/index，可以看到如下页面：</p>
<p><img class="aligncenter size-full wp-image-12613" src="https://blog.gmem.cc/wp-content/uploads/2015/02/book_index.png" alt="book_index" width="535" height="475" /></p>
<div class="blog_h3"><span class="graybg">避免URL硬编码</span></div>
<p>在上一节的模板中，我们有如下一段代码：</p>
<pre class="crayon-plain-tag">&lt;td&gt;&lt;a href="/bookmgr/book/{{ book.id }}"&gt;{{ book.name }}&lt;/a&gt;&lt;/td&gt;</pre>
<p>上面的href属性中，硬编码了工程名称和应用名称，这会导致部署时修改URL困难。你随时可以url标签避免硬编码：</p>
<pre class="crayon-plain-tag">&lt;td&gt;&lt;a href="{% url 'book_view' book.id %}"&gt;{{ book.name }}&lt;/a&gt;&lt;/td&gt;</pre>
<p>其中book_view就是URLConf中映射条目的name字段：</p>
<pre class="crayon-plain-tag">urlpatterns = [
    url(r'^book/(?P&lt;book_id&gt;[0-9]+)', views.book_view, name = 'book_view')
]</pre>
<div class="blog_h3"><span class="graybg">查看Book</span></div>
<p>上一节我们开发的视图展示了最近10条书籍信息，其中书籍名称具有链接，很明星，此链接点击后应该显示单本书籍的详细信息。</p>
<p>注意详细信息视图的URL设计：http://127.0.0.1:8000/bookmgr/book/view/4，仍然是RESTful风格的URL，最后的数字是书籍的主键。 我们可以这样映射URL：</p>
<pre class="crayon-plain-tag">url(r'^book/view/(?P&lt;book_id&gt;[0-9]+)', views.book_view, name = 'book_view')</pre>
<p>其中<pre class="crayon-plain-tag">(?P&lt;group_name&gt;...)</pre>  是正则式中的命名分组，使用命名分组后，可以把分组名作为视图函数的入参，其运行时值等于该命名分组的捕获。</p>
<p>视图函数如下：</p>
<pre class="crayon-plain-tag"># book_id这个入参名称必须和分组的命名一致
def book_view(request, book_id):
    try:
        book = Book.objects.get(pk = book_id)
    except Book.DoesNotExist:
        # 触发异常，Django会自动将其转换为404响应
        raise Http404('No such book:%i' % book_id)

    # 上面的try-except块也可以使用快捷方式代替：
    book = get_object_or_404(Book, pk = book_id)

    return render(request, 'book/view.html', {'book': book})</pre>
<p>模板代码如下：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;View Book&lt;/title&gt;
    {% load static from staticfiles %}
    &lt;link rel="stylesheet" href="{% static 'css/default.css' %}"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div&gt;&lt;span&gt;Detail information of: {{ book.name }}&lt;/span&gt;&lt;/div&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;Book name:&lt;/th&gt;
        &lt;td&gt;{{ book.name }}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;Category:&lt;/th&gt;
        &lt;td&gt;{{ book.category.name }}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;ISBN:&lt;/th&gt;
        &lt;td&gt;{{ book.isbn }}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;Publish date:&lt;/th&gt;
        &lt;td&gt;{{ book.pub_date }}&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>打开：http://127.0.0.1:8000/bookmgr/book/view/1，可以看到如下页面：</p>
<p><img class="aligncenter size-large wp-image-12625" src="https://blog.gmem.cc/wp-content/uploads/2015/02/book_view.png" alt="book_view" width="480" height="195" /></p>
<div class="blog_h2"><span class="graybg">图书管理模块V4</span></div>
<p>本节我们了解一下Django中的表单处理。 </p>
<div class="blog_h3"><span class="graybg">编辑Book</span></div>
<p>添加视图函数：</p>
<pre class="crayon-plain-tag">def book_edit(request, book_id, messages=[]):
    categories = Category.objects.all()
    try:
        book = Book.objects.get(pk=book_id)
    except Book.DoesNotExist:
        raise Http404('No such book:%i' % book_id)
    return render(request, 'book/edit.html', {'book': book, 'categories': categories, 'messages': messages})</pre>
<p>URL映射如下：</p>
<pre class="crayon-plain-tag">url(r'^book/edit/(?P&lt;book_id&gt;[0-9]+)', views.book_edit, name='book_edit')</pre>
<p>模板代码如下，注意其中的for和if控制结构：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Edit Book&lt;/title&gt;
    {% load static from staticfiles %}
    &lt;link rel="stylesheet" href="{% static "css/default.css" %}"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div&gt;&lt;span&gt;Edit: {{ book.name }}&lt;/span&gt;&lt;/div&gt;
{% if messages %}
    {% for msg in messages %}
        &lt;div class="{% if msg.ok %}ok{% else %}error{% endif %}"&gt;{{ msg.message }}&lt;/div&gt;
    {% endfor %}
{% endif %}
&lt;form action="{% url 'book_commit' %}" method="post"&gt;
    &lt;table&gt;
        &lt;tr&gt;
            &lt;th&gt;Book name:&lt;/th&gt;
            &lt;td&gt;
                {% csrf_token %} {% comment %} 必须，否则报错：CSRF verification failed. Request aborted. {% endcomment %}
                &lt;input type="hidden" name="id" value="{{ book.id }}"/&gt;
                &lt;input type="text" name="name" value="{{ book.name }}"/&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;Category:&lt;/th&gt;
            &lt;td&gt;
                &lt;select name="category"&gt;
                    {% for c in categories %}
                        &lt;option value="{{ c.id }}"
                                {% if c.id == book.category.id %}selected="selected"{% endif %}&gt;{{ c.name }}&lt;/option&gt;
                    {% endfor %}
                &lt;/select&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;ISBN:&lt;/th&gt;
            &lt;td&gt;
                &lt;input type="number" name="isbn" value="{{ book.isbn }}"/&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;Publish date:&lt;/th&gt;
            &lt;td&gt;
                &lt;input type="date" name="pub_date" value="{{ book.pub_date.isoformat }}"/&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/table&gt;
    &lt;input class="submit-btn" type="submit" value="Submit"/&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>点击首页的Action列的按钮 ，即可看到此编辑Book的页面：</p>
<p><img class="aligncenter size-full wp-image-12626" src="https://blog.gmem.cc/wp-content/uploads/2015/02/book_edit.png" alt="book_edit" width="326" height="232" /></p>
<div class="blog_h3"><span class="graybg">编辑Book后提交</span></div>
<p>点击上面的Submit按钮后，需要有服务器程序来处理表单数据，并返回处理结果。这些工作由book_commit视图函数完成： </p>
<pre class="crayon-plain-tag">def book_commit(request):
    # 类似于字典的、存放所有POST请求参数的对象。类似的 request.GET 用于访问GET请求参数
    data = request.POST
    messages = []
    categories = Category.objects.all()
    try:
        book_id = int(data['id'])
        category_id = int(data['category'])
        book = Book.objects.get(pk=book_id)
        category = Category.objects.get(pk=category_id)
        book.name = data['name']
        book.category = category
        book.isbn = int(data['isbn'])
        from dateutil.parser import parse
        book.pub_date = parse(data['pub_date'])
        messages.append({'ok': True, 'message': 'Book updated: %i' % book_id})
    except Book.DoesNotExist:
        messages.append({'ok': False, 'message': 'No such book: %i' % book_id})
    except Category.DoesNotExist:
        messages.append({'ok': False, 'message': 'No such category: %i' % category_id})
    except BaseException as e:
        print e.message
        messages.append({'ok': False, 'message': 'Failed to update book, %s' % e.message})
    return book_edit(request, book_id, messages)</pre>
<p>注意最后的return语句调用了其它视图函数，这类似于Java Servlet中的请求转发。另外一种处理方式是301重定向：</p>
<pre class="crayon-plain-tag"># reverse用于避免硬编码URL
return HttpResponseRedirect(reverse('book_edit', args=(book.id,)))</pre>
<p>这样也可以把用户带回到编辑页面。区别有两点：</p>
<ol>
<li>重定向方式下，不能方便的传递模板上下文变量。例如messages变量</li>
<li>重定向方式下，实际上就是让浏览器重新定位到book_edit这个URL，因此浏览器地址是/bookmgr/book/edit/3。前一种方式的浏览器地址是/bookmgr/book/commit</li>
</ol>
<div class="blog_h2"><span class="graybg">部署工程</span></div>
<p>完成开发后，我们需要把工程部署到生产环境的服务器上。</p>
<div class="blog_h3"><span class="graybg">Apache+mod_wsgi（嵌入式）</span></div>
<p>最简单的配置：</p>
<pre class="crayon-plain-tag"># /librarymgr/为URL前缀。匹配的URL将由wsgi.py处理
WSGIScriptAlias /librarymgr/ /home/alex/Python/projects/pycharm/librarymgr/librarymgr/wsgi.py
# PYTHONPATH，指定为工程根目录
WSGIPythonPath /home/alex/Python/projects/pycharm/librarymgr

&lt;Directory /home/alex/Python/projects/pycharm/librarymgr/librarymgr&gt;
&lt;Files wsgi.py&gt;
    Require all granted
&lt;/Files&gt;
&lt;/Directory&gt;

Alias /static/ /home/alex/Python/projects/pycharm/librarymgr/static/
&lt;Directory /home/alex/Python/projects/pycharm/librarymgr/static/&gt;
    Require all granted
&lt;/Directory&gt;</pre>
<p>虚拟主机方式：</p>
<pre class="crayon-plain-tag">WSGIPythonPath /home/alex/Python/projects/pycharm/librarymgr
&lt;VirtualHost 127.0.0.1:80&gt;
    WSGIScriptAlias / /home/alex/Python/projects/pycharm/librarymgr/librarymgr/wsgi.py

    &lt;Directory /home/alex/Python/projects/pycharm/librarymgr/librarymgr&gt;
    &lt;Files wsgi.py&gt;
        Require all granted
    &lt;/Files&gt;
    &lt;/Directory&gt;

    Alias /static/ /home/alex/Python/projects/pycharm/librarymgr/static/
    &lt;Directory /home/alex/Python/projects/pycharm/librarymgr/static/&gt;
        Require all granted
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/django-study-note">Django学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/django-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
