2010年5月29日 星期六

Django 內建的 template

Django 內建的 template 定了一套完整的使用規範:
  • template 內不能 call function, a.b 會視情況解讀成  a.b, a[b], a.b()。所以不管 a 是 dictionary, list 還是其它物件, 「照理說」都能順利地拿到 b 的值。但若 b 是變數的話, 拿不到 a[b]。
  • filter 可以接受一個參數。比方說 my_list|join:', ' 相當於 ', '.join(my_list), 不過這裡的 join 是 Django 定的 template filter。透過 filter 能做到如同 Python function call 傳兩個參數。另外 filter 如預期般可以串起來, 像是 my_list|join:', '|safe。我常用的兩個例子是顯示 verbose_name用變數當 key 取出 dictionary 的值
  • template tag 可以接受多個參數, 想用函式做的事, 它都能做。唯一不同之處, 在於 template tag 定了一套 parse -> new node -> node.render 的流程。不像平時的習慣: 傳參數到函式, 函式直接回傳字串。詳情可以參考官網的說明
若有照 template 的規範寫, 應該能將 model、view (Django 的 controller)、template 切得很乾淨, template 的 helper function 一致放在 templatetags 目錄, 裡面有自制的 filter 和 template tag。但是 template tag 寫起來實在太冗了, 幾行可以搞定的事, 得拆成三個步驟做。另外我懷疑這樣是否會有效能的隱憂。

若沒照 template 的規範寫, 程式碼會變得比沒規範的情況更難懂。傳 class A 的物件 a 給 template 時, 就塞些 template 要用的東西到 a.x, a.y 裡, 而 x, y 不是 a 原始的 member field。或是在 A 裡寫和 A 不是很相關, 不接參數的函式, 目的只是要讓 template 可以取用值。

難怪 Django FAQ 裡有這麼一項: "I can't stand your template language. Do I have to use it?"。看到有人推 Jinja2 Mako。初步掃了一下語法, Jinja2 看起來滿漂亮的, Mako 看起來有點亂, 很像 Python 但又不是 Python。Jinja2 的 filter 可接受多個參數, 又有 macro 的語法, 應該可以輕鬆滿足常用需求。

不過稍微研究一下換 template 的事後, 發現有些 plugin 和內建 template 綁在一起, 如內建的 Django auth 和 django pagination。換用別的 template 就不能直接使用, 得自己用新的 template 改寫。想想還挺麻煩的。各家 template 也會有點小問題, 像 Jinja2 用到 ctypes 加速, Google App Engine 上沒有 ctypes, 造成 Jinja2 無法提供正確的錯誤訊息。還是先繼續用內建的 template 吧。

2010年5月27日 星期四

JAVA_HOME, ANT_HOME, Eclipse 和 Ubuntu

JAVA_HOME 和 ANT_HOME 是環境變數, 用來表示 Java 和 Ant 的相關檔存在那裡。Tomcat 和一些 Java App 會用 JAVA_HOME 來找 bin/java 或其它檔案。Ubuntu 上 JAVA_HOME 要設成 /usr/lib/jvm/java-6-sun。這篇有些相關說明。

Ubuntu 上 ANT_HOME 是 /usr/share/ant, ant 的執行檔放在 ANT_HOME/bin/ant, ANT_HOME/lib 下有些 ant 可能會用到的 jar 檔, 如 ANT_HOME/lib/ant-junit.jar。

這篇提到 Eclipse 有另外設 ANT_HOME, 會蓋掉 bash 的環境變數, 要從 window-> preferences ->ant -> rumtime 重設需要的東西。所以若在 Eclipse 裡執行 Ant 且有用到 ANT_HOME 的話, 要在 Eclipse 裡加入相關的 jar 檔到 Eclipse 內的 ant runtime, 比方加入 junit.jar 到 Eclipse 的 ant runtime。還有要記得在 runtime 裡設 ANT_HOME 為 /usr/share/ant。

2010年5月26日 星期三

JavaScript Closure 的運用: Memoization

JavaScript: The Good Parts ch4 後半提到 Closure 後, 一堆例子都滿有趣的, 4.15 提到 Memoization 的實作技巧。用遞迴的好處是寫起來直覺, 缺點是有可能重覆算算過的值而跑得過慢。像費氏數列就是很好的例子。Memorization 指用個 table 記住之前遞迴算過的值, 藉此減少不必要的遞迴。

上回練習寫 Memorization 已是高中的事了, 那時用 C 練程式競賽, 不過 C 沒有恰當的 scope 維護方式, Memorization 寫起來較醜一些, 表格得存在全域變數或函式內的靜態變數。JavaScript 配合 Closure 很漂亮地解決表格的 scope 問題, 以下是參考書上的範例練習寫的碼, 幾乎長得一樣啦。
var memorizer = function(memo, fundamental) {
    var shell = function (n) {
        if (memo[n] === undefined) {
            memo[n] = fundamental(shell, n);
        }
        return memo[n];
    };
    return shell;
};

var fib = memorizer([0, 1], function(shell, n) {
    return shell(n - 1) + shell(n - 2);
});

var factorial = memorizer([1], function(shell, n) {
    return n * shell(n - 1);
});

fib(5); // 5
fib(10); // 55
factorial(10); // 3628800

memorizer 的第二個參數的函式得多加一個 "shell" 欄位, 讓 memorizer 產生暫時的函式 shell, 再將它傳進目標函式, 這樣 fib /  factorial 裡用的 shell 就是有 memorization 效果的函式了。

初看的時候腦筋會打結吧, 看懂後覺得很有意思。

2010年5月25日 星期二

JavaScript 雜項心得

之後大概沒機會繼續玩 JavaScript, 整理一下之前看過的東西。

主要是看 Douglas Crockford 寫的文件。過去一直排斥學 JavaScript, 這回終於開始學了。沒想到意外地有趣, 感謝 Douglas Crockford 賣力地為它澄清, 讓我有機會重新認識它。

JavaScript

必讀文件:
  • coding convention
  • Google Tech Talk: JavaScript, the good parts。半小時而已, 看完立即明白 JavaScript 的問題, 避開它們後 JavaScript 其實是很美很強大的語言。
  • JsLint 的說明並試一試。可以協助記得 coding convention, 後來到是沒什麼在用。若有需長期維護的程式, 再拿來用吧。JsLint 可以幫忙列出函式內變數的可視範圍, 像是 global / closure / local, 頗方便的。
Douglas Crockford 寫了一些好文件, 下面是我看過覺得頗受用的幾篇:
另外就是讀他寫的書: JavaScript, the good parts。可惜我空閒時間花完了, 沒翻幾頁。之後有機會再來好好讀一讀。

jQuery

其實大部份時間都在寫 jQuery, 不過懂 JavaScript 語法的話, 寫起來比較順手, 不會被 JavaScript 的語法卡住。jQuery 真是我用過最容易上手功能又強大的函式庫。
  • jQuery 教學 - 基礎篇: 附許多例子, 大概掃一遍就能上工啦。
  • ericsk 寫的 tutorial: 有完整的例子可以跟著做。
  • 官網的 CSS Selector 文件: 要能活用 jQuery, 第一步就要能熟用 CSS Selector 選出目標物件。
  • jQuery 參考手冊: 官網文件寫得很清楚, 也有附範例碼。若要找函式名稱, visualjquery 也很方便, 可以快速濾出可能的函式。
  • jQuery UI 和 jQuery Tools 將 CSS, HTML 和 JavaScript 包好, 方便使用。像是附月曆輸入日期的 input tag, 或是 progress bar 等。
再來就是掃一遍 jQuery API, 或是視需求查一下相關的文件, 比方用 even / odd 可以一行搞定 table 奇偶列不同 css class。用 eq / index 可以找到列表中特定的元件。

QUnit

QUnit 是 jQuery 為了 unit test 而寫的函式庫。用法就是寫個 html 載入 QUnit 的 js、css, 就可以藉由瀏覽器讀網頁來執行 unit test 並將結果輸出在網頁裡。
  • 簡單易懂的例子。
  • 官網有不少例子可以參考, 像 fx.js 的寫法挺漂亮的, 用 jQuery 建出 test fixture 而不用綁在 html 上測。
若寫了不少 QUnit 想在 terminal 上執行, 可以透過 js-test-driver 執行。它只是一個 test runner, 有提供 QUnit 的 adaptor, 這樣就能用 terminal 跑 Java 程式, 透過一個執行中的瀏覽器自動跑 unit test。我是看 Miško Hevery 推薦才試用的, 只是我的 JavaScript 沒多到需要這麼測, 寫了一些 QUnit 測完較複雜的核心後, 就沒在用了。大多情況還是在 Firebug console 裡試成就就貼到 JavaScript 程式裡。待下次寫 JavaScript 時, 再來多挑戰用 TDD 開發。

Firebug

我一開始用 Chrome Developer Tools, 不過和 Firebug 交替用一陣子後, 感覺還是 Firebug 比較好用。像 DOM navigation 和調整 CSS 的部份 Firebug 好用多了。除之前在 CSS 心得提到的影片外, 再看《3 分鐘學會用 firebug 除錯》, 用 console.log() 輸出物件, 在 Firebug DOM 裡觀看細部資訊, 相當方便。

備忘

2010年5月21日 星期五

Sikuli 試用心得

由於 KMPlayer 設 Num+ 為加速播放的快速鍵, 而我用 Filco Tenkeyless, 沒有右側九宮格的小鍵盤。每次要加速播放就很麻煩, 要用滑鼠點點點的。從 100% 調到 130% 就要做六次「右鍵、移滑鼠點左鍵、移滑鼠點左鍵」, 做起來頗煩的。想說就來試試最近頗紅的 Sikuli

成果如下, 原始碼在這裡, 以後可省下不少力氣。


簡記試用的過程
  1. 裝好後無法執行 exe, 出現 "JNI Exception: failed to create the Java VMReason: not enough memory."。google 一下發現 Windows XP 下不少人遇到一樣的問題, 執行 bat 檔即可。但這樣匯出 exe 檔後也不能執行, 只能用在 IDE 裡執行。
  2. 執行簡單的操作一直遇到問題, 預設 IDE 留很少的空間給 console, 沒看到上面的錯誤訊息。找到 console 後看到錯誤訊息說找不到圖檔, 才發現不能存在中文目錄下。
  3. IDE 抓 screenshot 的功能滿方便的, 但不知要怎麼和右鍵搭配。先用右鍵顯示某個選單, 再回 IDE 內寫 code 抓 screenshot, 右鍵叫出的選單就沒了。後來的解法是先亂抓圖, 之後再自己用抓圖軟體 (如 PicPick ) 抓右鍵選單上的圖, 抓好後再存檔覆蓋剛亂抓圖的檔名。
  4. 執行速度頗慢的, 慢到我想自己按。不過設 region 為螢幕右下角 1/4 大小後快了不少, 就夠用了。
  5. Sikuli 用 Python 的語法, 不需學習, 大概摸索一下就會用了。所以我才能順利完成自動化工作, 省下的時間拿來愉快地寫 blog。
若執行速度能更快的話, 也許可以拿來衡接產品上一些測試工作。有機會再來試。

2010年5月18日 星期二

CSS 雜項心得

再過一陣子大概會大幅減少寫 web 的事, 趁最近有空檔陸續來記錄最近的心得, 方便日後回憶。

學 CSS 的幾個重點:
一些雜項心得:
  • 先載入 undohtml 去除所有 tag 原本的功能, 方便之後設定。google 一下 "undohtml" 或 "reset css" 可以找到一些範例來用。
  • 置中的方式是將 margin 的左右設成 auto。
  • colgroup 和 col 管理 table 的欄寬。
  • 用 jQuery 的 even / odd 可輕易將表格的奇偶欄設上 odd / even 的 class, 方便配色。
  • CSS 裡的 url 屬性是用相對於 CSS 檔的位置找檔案。
  • Give Up and Use Tables: 在找用 div 取代 table 的作法時發現這個站, 我聽從上面的建議, 很愉快地用 table 完成我該做的事。
日後有機會再來研究:
  • 如何模組化 CSS。網頁一多就很亂, 不好維護。和寫程式一樣, 要理出共用和各自延伸的 CSS code。
  • 適當的情況用 div / span 取代 table。讓資料可簡單地在表格和清單之間轉換呈現方式。

2010年5月10日 星期一

在 Django 內建 template 中存取 dict[key]

參考這篇的作法, 終於解掉心頭大恨。詳細問題見該篇文章最後的例子, 當 key 是變數時, 無法在 template 中用 dict.key 取值。解法是用 filter:
@register.filter(name='dict_get')
def dict_get(value, key):
    return value.get(key, '')  # 注意 filter 不能丟出 Exception

filter 的相關說明可參考之前的文章

實在是不知該說什麼。之前為了這問題, 我笨笨地用 AJAX 分多次取回我想要的值, 實在太蠢了。

除非又遇到解不掉的功能, 或是 template 太慢, 暫時不會想換 template, 有太多東西要學啦。

用 apache + mongrel cluster + relative url 跑 Redmine

由於公司防火牆設定問題, 想想還是把 Redmine 跑在 apache 裡共用 port 80, 對大家比較方便。今晚就試了一會兒, 沒想到意外設成功了, 真開心啊!!

我大致上是參考這篇。執行方式是 mongrel 自己跑 web server 聽幾個 port, 再用 apache 跑 proxy, 將特定路徑轉交給 mongrel server。摘要做法如下:
  1. 參考隨便一篇文章在 Ubuntu 上裝好 Redmine, 能用單機 mongrel_rails 執行。
  2. 安裝 mongrel_cluster:
    sudo gem install mongrel_cluster
  3. 設 REDMINE_HOME/config/mongrel_cluster.yml:
    user: www-data
    cwd: REDMINE_HOME
    port: "3000"
    environment: production
    group: www-data
    address: 0.0.0.0
    pid_file: log/mongrel.pid
    servers: 2
  4. 設 Redmine 的相對網址, 在 REDMINE_HOME/config/environments/production.rb 檔案最後加上
    config.action_controller.relative_url_root = '/redmine'
    比起 Django 真是簡單百倍。
  5. 執行 mongrel cluster:
    cd REDMINE_HOME
    mongrel_rails cluster::start
    成功的話會跑在 port 3000 和 3001。沒成功的話可以讀 REDMINE_HOME/log/mongrel.3000.log 了解原因。 REDMINE_HOME 內所有檔案擁有者和群組記得設成 www-data。
  6. 安裝和啟動 apache 需要的 modules
    a2enmod proxy
    a2enmod proxy_http
    a2enmod proxy_balancer
    a2enmod rewrite
    
  7. 設定 apache conf, 我是寫在 /etc/apache2/conf.d/011_redmine.conf 裡:
    <Proxy *>
    Order deny,allow
    Allow from all
    </Proxy>
    
    <Proxy balancer://redmine_cluster>
    BalancerMember http://YOUR_IP:3000
    BalancerMember http://YOUR_IP:3001
    </Proxy>
    
    ProxyPreserveHost On
    
    <Location "/redmine">
        ProxyPass balancer://redmine_cluster
    
        RewriteEngine On
        # Redirect all non-static requests to cluster
        RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
        RewriteRule ^/(.*)$ balancer://redmine_cluster%{REQUEST_URI} [P,QSA,L]
    </Location>
    
    注意預設 apache proxy 是 deny all, 沒打開 apache error log 會出現 "client denied by server configuration ..."
  8. 執行
    /etc/init.d/apache2 reload

目前沒設 /etc/init.d/mongrel_cluster, 以後有機會再來研究。

2010年5月7日 星期五

CSS 覆寫屬性的規則

之前我都很蠢地在最上層用 <div id='content'> 包住所有內容, 然後在 css 裡寫 #content div { ... } 定基本共通的屬性。結果當 <div id='content'> 裡面需要用某個特別的 div 時, 怎麼設都無法取代之前設的屬性。我以為後寫的會覆蓋先寫的 (last match), 但看來又不全然如此。今天查了一下, 看到這篇說愈明確的 css selector 的優先權愈高。

於是改用 <div class='content'> 和 .content div { ... } 後就解決了。之後要在內部改變 div 屬性時, 萬一怎麼都搞不定, 至少用 id 可以蓋過去。難怪看到 jQuery UI 裡都用一堆 class, 沒事還是別用 id 設 css, 之後再來找時間研究一下較好的配套作法。


2010年5月6日 星期四

制作簡單的 API: 在 Django 中回傳 JSON

將 HttpResponse 的 mimetype 設為 json 即可, 程式如下:
# views.py
def myapi(request):
    data = {
       'name': 'Alice',
        ...
    }
    return HttpResponse(simplejson.dumps(data), mimetype='application/json')

設好 urls.py 後 (/myapi/), 就能用 jQuery 的 $.ajax 輕鬆地 call /myapi/ 取得 JSON ( callback function 的參數會是 JavaScript object, 不用再呼叫 JSON.parse )。為了減少出錯的機會, 我將 ajax 的參數 aysnc 設為 false。

回傳值是 JSON 有許多好處, 除方便用 AJAX 拿資料外, 也方便寫 unit test:
class MyApiTest(django.test.TestCase):
    data = { ... }
    response = self.client.post('/myapi/', data)
    self.assertEqual(200, response.status_code)
    expected = { ... }
    self.assertEqual(expected, simplejson.loads(response.content))

附帶一提, 有人寫了個 decorator 將一般的 view 轉成回傳 json, 目前沒這需求, 留著備忘。

2010年5月5日 星期三

django + mod_wsgi + virtualenv 注意事項

官網文件寫得滿清楚的, 這裡摘錄重點:
  • 為了避免用到系統裝的 package, 要將 virtualenv 載入的 package 放在 sys.path 前面, 這有幾種解法。
  • 第一種做法是在 apache config 裡寫: WSGIPythonHome /usr/local/pythonenv/BASELINE。BASELINE 是自己建的空 virtualenv。如此一來  mod_wsgi 就不會載入系統裝的 package, 自然沒這問題。
  • 但若 apache 同時用了 mod_python 和 mod_wsgi 的話, WSGIPythonHome 會失效 ( 我之前裝的 ReviewBoard 就是用 mod_python, 不知後來如何)。這裡有一點相關說明。
  • 在沒設 WSGIPythonHome 的情況下, 執行 mod_wsgi 時, sys.path 已載入系統裝的 packages 了。在這種情況下有兩種解法。
  • 若用了 mod_wsgi 2.4 之後的版本, mod_wsgi 有修改 site.addsitedir(), 讓它把 package path 加到 sys.path 的前面, 所以不用多做任何事, 就沒有這個問題。
  • 但是 Ubuntu 8.04 用的是 mod_wsgi 2.0, 所以得採用另一個作法, 也就是官網在 Application Environments 章節最後那段程式, 呼叫 site.addsitedir() 後自己重排 sys.path 內的順序。
  • 另外一點, 若只有一組 virtualenv, Application Environments 和 Process Environments 兩者做一個就行了。前者是在 WSGI  script (也就是 python script) 裡加 site-packages 到 sys.path 裡;後者是用 apache directive 指定 python path。
  • Process Environments 一次設好全部 WSGI application 的 virtualenv。但若有多個 WSGI application 需要不同的 virtualenv, 就得用 Application Environments 的方式各別設定。比方說 project A 用 Django-1.1, project B 用 Django-1.2, 那就得建兩個 virtualenv, 用 Application Environments 的方式各別設。
我最後是用 WSGIPythonHome + Application Environments + Daemon mode 的作法搞定。
附帶一提, 官網說一般情況 embedded mode 效能可能較好 (但官網也說應該差不了多少)。而我選 daemon mode 是因為這樣改程式後要重載入的話, touch WSGI script 即可, 不用 apache reload。

2010年5月2日 星期日

用 PGP 驗證下載的壓縮檔

剛才下載 Hadoop 的東西來試, 看到一些疑似驗證的檔案 KEYS 和 hadoop-0.20.2.tar.gz.asc。雖說不理它也可以繼續做事, 但看到神祕的檔案卻沒弄清楚, 感覺挺彆扭的。以前只試過比對 md5 checksum, 這回是第一次用 PGP。上網查了一下, 大部份文章都說它用在送信、運用 public-private key 來做加解密、以及一些信件相關軟體, 卻沒提到下載檔案的例子。後來靈機一動, 查 "tar.gz.asc" 查到這篇文章, 才找到使用的例子。

在 Ubuntu 下的操作過程:
  1. sudo aptitude install pgpgpg  # 安裝 gpg
  2. 下載 KEYS、hadoop-0.20.2.tar.gz.asc、hadoop-0.20.2.tar.gz
  3. gpg --import KEYS  # 載入壓 hadoop tarball 作者的 PGP public keys
  4. gpg --verify hadoop-0.20.2.tar.gz.asc  # 驗證 tarball 的 signature
試的結果, gpg --verify X.asc 後會拿 X 來計算看看 signature 是否一致。

2010年5月1日 星期六

Django URL 相對路徑的設法

看似很單純的事, 結果做起來有夠麻煩, 不知大家是怎麼做的? 網路上的範例幾乎都是將 project 放到根路徑, 即 http://.../。但若想在一台機器上放多個 project 的話, 就得額外處理。可能的做法有:
  • 註冊多個 domain name, 配合 apache 的 virtualhost 將不同 domain name 對到不同的 project。如此一來全部 project 都能用根路徑。
  • 將 project 放到子路徑: http://.../foo/, http://.../bar/。
我不想申請 domain name, 所以就選第二個方案。需要更改的東西有:
  • settings.py
  • urls.py
  • myapp/views.py
  • template/*.html
  • apache conf
做法如下。

settings.py

# 不同 project 設不同的 site_root, 像是 '/foo/' 或 '/bar/'
SITE_ROOT = '/'
MEDIA_URL = SITE_ROOT + 'media/'
ADMIN_MEDIA_PREFIX = SITE_ROOT + 'admin_media/'
# auth 要用到的 URL 設定
LOGIN_URL = SITE_ROOT + 'login/'
LOGIN_REDIRECT_URL = SITE_ROOT
在 views 裡記得用 RequestContext, 這樣 template 裡才能直接取用 {{ MEDIA_URL }}。

urls.py

# 路徑都改用 url 設名字, 之後才能在 template 中用 url tag 指回 view。
url(r'^$', views.index, name='index'), 
url(r'^login/$', 'django.contrib.auth.views.login', name='login'),
url(r'^logout/$', 'django.contrib.auth.views.logout_then_login', name='logout'),
...
# 順便提一下, 跑在 production server 上時會直接用 apache 傳靜態檔案, 
# 但開發時是用 Django 的 web server, 所以要要自己處理靜態檔案
if settings.DEBUG:
    # Define url routes for static files in development mode.
    urlpatterns += patterns('django.views.static',
                            (r'^media/(?P<path>.*)$',
                             'serve', {
                                 'document_root': settings.MEDIA_ROOT,
                                 'show_indexes': True}),)

myapp/views.py

一般情況直接 call views 中的 function 名稱, 但用 HttpResponseRedirect 時就得用 SITE_ROOT, 比方像這樣:
@login_required()
def some_page(request, project_id):
    if not request.user.is_staff:
        return HttpResponseRedirect(settings.SITE_ROOT)
    ...

template/*.html

url tag, 比方說:
<a href="{% url index %}">Homepage</a>

apache conf

我設在 /etc/apache2/conf.d/MY_DJANGO_CONF.conf。大致設法和 Django 用 mod_wsgi 差不多, 只是改一下路徑:
# Alias other operations to wsgi script.
WSGIScriptAlias /foo FILE_PATH_TO_WSGI_SCRIPT
WSGIScriptAlias /bar FILE_PATH_TO_WSGI_SCRIPT

<Location "/foo/media">
    SetHandler None
</Location>

<Location "/bar/media">
    SetHandler None
</Location>

Alias /foo/media FILE_PATH_TO_MEDIA
Alias /bar/media FILE_PATH_TO_MEDIA

# 若用 virtualenv 的話就指到 virtualenv 內的 site-packages
Alias /foo/admin_media /usr/lib/python2.5/site-packages/Django-1.1.1-py2.5.egg/django/contrib/admin/media
Alias /bar/admin_media /usr/lib/python2.5/site-packages/Django-1.1.1-py2.5.egg/django/contrib/admin/media

2010-05-06 更新

今天發現要在 JavaScript 內用 AJAX 連線, 於是得在 JavaScript 裡取得 settings.SITE_ROOT, 作法如下:
  • 在 settings.py 裡註冊一個 context processor:
    TEMPLATE_CONTEXT_PROCESSORS = (
        'django.core.context_processors.auth', # 頭四個是預設的, 覆寫 TEMPLATE_CONTEXT_PROCESSORS 時記得加進去
        'django.core.context_processors.debug',
        'django.core.context_processors.i18n',
        'django.core.context_processors.media',
        'mysite.myapp.views.add_constants',  # 自己用的
    )
  • 在 views.py 裡寫 add_constants:
    def add_constants(request):                                            return {
            'SITE_ROOT': settings.SITE_ROOT,
        }
    
    這兩步的目的是讓每個 view 都傳 SITE_ROOT 到 RequestContext 裡。
  • 在 template 裡置入 SITE_ROOT 的值, 寫到 base.html 裡:
    <script language="javascript" type="text/javascript">
        my_app.site_root = '{{ SITE_ROOT }}'; // my_app 是 global object, 作為我的 js code 的 root object
    </script>
    
    如此一來, 就能用 my_app.site_root 取得值。
真不是普通的麻煩啊。

寫個 filter 取出 model 欄位中的 verbose_name

查了一下 Django 好像沒內建這個功能, 要自己從 model 的 _meta 取出 field 的 verbose_name。就自己寫了個 filter:
# mysite/myapp/templatetags/myapp_extras.py
from django import template
from django.db.models.fields import FieldDoesNotExist

from mysite.myapp import models

register = template.Library()

@register.filter(name='verbose_name')
def verbose_name(value):
    model_name, field_name = value.split('.')
    model = getattr(models, model_name, None)
    if not model:
        return ''
    try:
        return model._meta.get_field(field_name).verbose_name
    except FieldDoesNotExist, e:
        return ''
在 template 裡的用法:
{% load myapp_extras %}
...
{% mymodel.myfield|verbose_name %}
...
filter 的寫法可以參見官網的《Custom template tags and filters》, 滿簡單的。

附帶一提, 要從字串拿出 model 時, 我楞了一下, 才發覺過去都沒想過「Python reflection API」。第一印象想到的是這種東西:
exec('model = %s' % model_name)
後來才想到不就是用 getattr 嗎..., 簡單到忘了它就是 reflection API。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...