發表文章

目前顯示的是 五月, 2010的文章

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 …

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。

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 效果的函式了。

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

JavaScript 雜項心得

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

主要是看 Douglas Crockford 寫的文件。過去一直排斥學 JavaScript, 這回終於開始學了。沒想到意外地有趣, 感謝 Douglas Crockford 賣力地為它澄清, 讓我有機會重新認識它。
JavaScript必讀文件: coding conventionGoogle Tech Talk: JavaScript, the good parts。半小時而已, 看完立即明白 JavaScript 的問題, 避開它們後 JavaScript 其實是很美很強大的語言。讀 JsLint 的說明並試一試。可以協助記得 coding convention, 後來到是沒什麼在用。若有需長期維護的程式, 再拿來用吧。JsLint 可以幫忙列出函式內變數的可視範圍, 像是 global / closure / local, 頗方便的。Douglas Crockford 寫了一些好文件, 下面是我看過覺得頗受用的幾篇: reduce global variables / functions by one big root variable: 說明如何避免 global variable 並將程式結構化。for in: JavaScript foreach 的正確寫法。private method / member by Closure: Closure 真是太酷啦!另外就是讀他寫的書: JavaScript, the good parts。可惜我空閒時間花完了, 沒翻幾頁。之後有機會再來好好讀一讀。 jQuery其實大部份時間都在寫 jQuery, 不過懂 JavaScript 語法的話, 寫起來比較順手, 不會被 JavaScript 的語法卡住。jQuery 真是我用過最容易上手功能又強大的函式庫。
jQuery 教學 - 基礎篇: 附許多例子, 大概掃一遍就能上工啦。ericsk 寫的 tutorial: 有完整的例子可以跟著做。官網的 CSS Selector 文件: 要能活用 jQuery, 第一步就要能熟用 CSS Selector 選出目標物件。jQuery 參考手冊: 官網文件寫得很清楚, 也有附範例碼。若要找函式名稱, visualjquery 也很方便, 可以快速濾出可能的函式…

Sikuli 試用心得

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

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


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

CSS 雜項心得

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

學 CSS 的幾個重點:
W3Schools CSS Tutorial。就算不看它, 平時搜 CSS 語法也會常查到這裡, 附方便的範例程式試用。學會用 Firebug: 教學 | 如何使用firebug即時預覽css。工欲善其事, 必先利其器。花個六分鐘看完這份 screencast 後, 之後調 CSS 超輕鬆的, 先在 Firebug 裡調到好再回去看原始檔。了解 CSS 覆寫的規則。了解 float、clear 的功用。比方說參考這篇《CSS 排版觀念:Float》。要將 div、span 這類無特定排版功能的 tag 排好, float 是必備屬性。該網站另有不少好文, 像 《CSS 技巧:表單》提到 clear 的例子, 還有用 float 後就能設 width 和 height 了。The Principles Of Cross-Browser CSS Coding 介紹 CSS 的基本觀念和跨瀏覽器的注意事項。文中提到不少延伸閱讀, 如 CSS Differences in Internet Explorer 6, 7 and 8 和 The Mystery Of The CSS Float Property。這幾篇雖然較長, 文章的品質非常高, 相當值得讀。Floatutorial 提供豐富的範例說明 float 和非 float 元件的顯示規則。最後一個範例一步步說明如何做出三欄的結構, 簡單易懂。HTML tag 可以有多個 class, 善用 class 組合屬性。jQuery UI 有定一套設定規範, 之後有機會再來深入研究這塊。jQuery UIjQuery Tools 有不少好東西, 像是選日期、單選多個元件、progress bar 等。這些套件將 client 端的 CSS 和 JavaScript 綁好了, 並提供可客制的外觀。一些雜項心得: 先載入 undohtml 去除所有 tag 原本的功能, 方便之後設定。google 一下 "undohtml" 或 "reset css" 可以找到一些範例來用。置中的方式是將 margin 的左右設成 auto。用 colgroup 和 col 管理…

在 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。摘要做法如下:
參考隨便一篇文章在 Ubuntu 上裝好 Redmine, 能用單機 mongrel_rails 執行。安裝 mongrel_cluster: sudo gem install mongrel_cluster設 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設 Redmine 的相對網址, 在 REDMINE_HOME/config/environments/production.rb 檔案最後加上config.action_controller.relative_url_root = '/redmine'比起 Django 真是簡單百倍。執行 mongrel cluster: cd REDMINE_HOME mongrel_rails cluster::start成功的話會跑在 port 3000 和 3001。沒成功的話可以讀 REDMINE_HOME/log/mongrel.3000.log 了解原因。 REDMINE_HOME 內所有檔案擁有者和群組記得設成 www-data。安裝和啟動 apache 需要的 modulesa2enmod proxy a2enmod proxy_http a2enmod proxy_balancer a2enmod rewrite 設定 apache conf, 我是寫在 /etc/apache2/conf.d/011_redmine.conf 裡:<Proxy *> Order deny,allow A…

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, 之後再來找時間研究一下較好的配套作法。


制作簡單的 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, 目前沒這需求, 留著備忘。

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 用 Dja…

用 PGP 驗證下載的壓縮檔

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

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

Django URL 相對路徑的設法

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

寫個 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。