]> Panopticon :: 2007年4月 Archive

Lv2

文字列中に一度しか出現しない文字を抜き出す。

ex. "aabcccd" -> "bd"

rareCharFilter :: String -> String
rareCharFilter [] = []
rareCharFilter (x:xs)
 | any (x ==) xs = rareCharFilter $ filter (/= x) xs
 | otherwise = x : rareCharFilter xs

main :: IO()
main = do x <- getContents
          putStrLn $ rareCharFilter x

rareCharFilter内で再帰しているため、^DでEOFを送ってやるかファイルからリダイレクトする必要がある。

入力から一文字とって、その文字が以降の文字列に出現していれば削除、出現していなければ残して次、と繰り返すだけ。

feedから記事内容が取得できない原因が判明しました。

テンプレートの

{% load rss_get %}
<html>
<head>
<title>Admin's RSS Reader</title>
</head>
<body>
{% for site in object_list %}
   <h1>{{ site.title }}</h1>
   <p>{{ site.rss }}</p>
   {% rss_get site.rss as entries %}
   {% for entry in entries %}
      <h2>{{ entry.title }}</h2>
   {% endfor %}
{% endfor %}
</body>
</html>

のカスタムタグrss_get部分で、僕はてっきりsite.rssが評価され、URIに変換された後にrss_getが呼びだされるものと思っていた。がどうやら違ったようで、"site.rss"という文字列がそのままタグの引数として渡されている。当然フィードは見付からず、feedparserはNot foundを返す。インタープリタとDjangoで返ってくるものが違うのにはそういう理由があったわけ。わかってみれば納得です。

しかしなぜrss_getの引数site.rssが評価されないのか?その直前にある{{ site.rss }}ではちゃんとデータベースから拾ってきたURIが表示されているのに。組み込みタグで引数をとれるもの(ifとか)の実装をみても、別に内部で変なことをやっているわけではない、普通に処理しているだけだから、この短いテンプレートのどこかになんかまずい点があるようだ。

怪しいのはここかController部分だと思うんだけど…連休中には解決したい。

(追記)寝る前に考えていたらforタグ内でパースしている部分が怪しい気がしてきた。

(追記)template冒頭の{% load ... %}タグが抜けていました。

(追記)解決しました。

Python Challenge

を空き時間にちょこちょこやっている。趣旨としてはPythonを使って問題にチャレンジ、というものなんだろうけど、ここは敢えてHaskellで攻めたいと思う。僕のHaskellレベルはたぶん1.5くらい。

Lv1

アルファベット小文字列を二文字循環シフトする。

ex. "x ( yz )" -> "z ( ab )"

import Char

shift :: Char -> Char 
shift a
 | a == 'z' = 'a'
 | isLower a = chr $ ord a + 1
 | otherwise = a

main :: IO()
main = do x <- getContents
          putStr $ last $ take 3 $ iterate (map shift) x

はじめての Django アプリ作成,その 3を参考にさせていただき、外部公開用のViewをつくる。データベースから情報をひっぱってくるのはすぐにできた。次は各サイトごとにRSSに含まれている記事を展開したい。そのためにはDjangoのテンプレート言語を勉強する必要がありそうだ。

>>> from django.template import Context,Template 
>>> import feedparser 
>>> rss = "http://www.panopticon.jp/blog/index.xml"
>>> c = Context({"entries":feedparser.parse(rss).entries})
>>> t = Template("{% for entry in entries %} \
... {{ entry.title }} \ 
... {% endfor %}") 
>>> t.render(c) 

とやるとRSS中の記事タイトル一覧が表示される。要するにTemplate中で使いたい変数をContextの中にしまっておけば、あとはDjangoが勝手に展開してくれるというわけですね。

とりあえずこんな感じでTemplateを作ってみる。site.titleとsite.rssにはデータベースの値がそのまま入る。rss_getタグは指定されたRSSから記事のリストを取得しentriesによって参照できるようにする。それぞれの記事を表示するにはentriesについてループを回してやればよい。

<html>
<head>
<title>Admin's RSS Reader</title>
</head>
<body>
{% for site in object_list %}
   <h1>{{ site.title }}</h1>
   <p>{{ site.rss }}</p>
   {% rss_get site.rss as entries %}
   {% for entry in entries %}
      <h2>{{ entry.title }}</h2>
   {% endfor %}
{% endfor %}
</body>
</html>

Python プログラマのための Django テンプレート言語ガイドを見ながらrss_getタグを作る……がうまくいかない。何も表示されない。

from django import template
from rss.site.models import Site
import feedparser

register = template.Library()

def do_rss_get(parser, token):
        bits = token.contents.split()
        if len(bits) != 4:
                raise template.TemplateSyntaxError, "'%s' requires 3 arguments" % bits[0]
        if bits[2] != "as":
                raise template.TemplateSyntaxError, "'%s' 's 2nd argument must
be 'as'" % bits[0]
        return RSSNode(bits[3], bits[1])

class RSSNode(template.Node):
        def __init__(self, variable, rss):
                self.variable = variable
                self.rss = rss
        def render(self, context):
                f = feedparser.parse(self.rss).entries
                context[self.variable] = f

RSSNodeを

        def render(self, context):
                f = feedparser.parse(self.rss)
                context[self.variable] = f

としてやると、Feed, Encoding,Bozo, Version, Namespaces, Entries, Bozo_ExceptionとRSSの各属性を拾って来た。いちおうタグとして動いてはいるようだ。

>>> c = Context({"entries":feedparser.parse(rss)})
>>> t.render(c)
' Feed  Status  Updated  Version  Encoding  Bozo  Headers  Etag  Href  Namespaces  Entries '

とインタープリタで実行した時のほうが多くの属性を拾ってくる。なぜ。続く。

JITEC テクニカルエンジニア(データベース)試験 解答

午前は半分くらい過去問だろうと思っていたのに、ほとんどが見たことのない問題だった。結果45/55。まーどちらにしろ午後1で轟沈したわけですが。言い訳になるが午後1は日本語がどうとかいうレベルではなかった。来年に備えてまた頑張ります。

とりあえず午後2はこんな感じで書いてみました。解答の正しさは一切保証できません。

午後2 問1

1 - (1)
プロジェクトPL対応 (プロジェクト番号, 部コード, PL社員番号)
職位ランクコード単価 (職位ランクコード, 年度, 単価)
人件費コスト予算 (プロジェクト番号, 部コード, 年度, 年月, 職位ランクコード, 予算工数)
人件費コスト実績 (プロジェクト番号, 部コード, 年度, 年月, 職位ランクコード, 実績工数)
人件費以外コスト予算 (プロジェクト番号, 部コード, 年度, 年月, 費目コード, 予算金額)
売上予算 (プロジェクト番号, 部コード, 年度, 売上予算金額)

1 - (2)
訂正日付から現在までの間に当該社員の職位ランクが変更された場合。(31字)
テーブル : 稼働実績明細
列名 : 職位ランクコード

2 - (1)
-R-- -R-- -R--
-R-- -R-- -R--
-R-- -R-- ----
---- -RU- -RU-
-R-- ---- -R--
CRUD -RU- ----

2 - (2)
勤務実績入力機能と稼働実績入力機能は分割し稼働実績入力機能と稼働実績訂正機能は統合しない。(45字)
勤務実績と稼働実績は別のテーブルで管理されており、分割すべきでない業務上の必然性もないため指摘事項に従って分割を行う。稼働実績入力機能と稼働実績訂正機能を統合した場合、訂正の際に改めてすべての稼働実績明細を入力し直す手間が生じる。従って統合は行わない。(126字)

3 - (1)
締め対象月より前の勤務実績が履歴として別テーブルに移されるため、社員それぞれが更新を行う勤務実績エントリのテーブルサイズが小さくなり、レスポンスが向上する。(78字)

3 - (2)
月別勤務状況推移表出力 : (c)
部コード、年月を主キーとし、勤務状況推移表の作成に必要な平均休日出勤日数、平均超過勤務時間、平均深夜勤務時間といった属性をもつテーブルを新たに作成する。(76字)
あらかじめ上のテーブルを作成しておくことにより、日別勤務実績や所属から部別、月別の勤務状況を結合、集計する時間が短縮される。(62字)
部長により月間勤務状況が承認される際、部署全体での勤務時間を集計し、上で作成したテーブルに追加する。(50字)
プロジェクト予算実績管理表出力 : (c)
部コード、年度ごとに、人件費および人件費以外のコストについて、予算、実績をそれぞれ属性とするテーブルを新たに作成する。(59字)
あらかじめ上のテーブルを作成しておくことにより、人件費等コスト実績などのテーブルから部別、年度別のプロジェクト予算実績を結合、集計する時間が短縮される。(76字)
それぞれの部署について年度ごとに予算や実績が確定するタイミングで集計を行い、上で作成したテーブルに追加する。(54字)

・問題文をよく読む
・キーと関数従属、正規化
・スーパータイプ、サブタイプ
・データの実体、業務の流れを意識する

結局のところこのへんに気をつければ合格点はとれるはずなのだが、どうも脳のキャパシティと注意力が足りなくていけない。正直最初は無勉でいけるんじゃないかと思っていたのだがその見通しは甘すぎた。試験当日にかつてない勢いでセロトニンが大量分泌されることを祈りたい。とはいえ、合格率8%という数字が示すほど難しい試験ではない。実務経験がなくても十分戦えるはずなので、最後まで諦めず全力を尽くそうと思う。

おやすみなさい。

現在の状況を整理すると、

タイトル欄に直で日本語を入力→登録される

フィードから日本語の含まれるタイトルを取得→エンコードエラー

エラーメッセージは

UnicodeEncodeError at /admin/sites/site/add/
'latin-1' codec can't encode characters in position 0-2: ordinal not in range(256)

となっているため、どこかUnicodeにすべき部分がもれているのでしょう。

通常のサーバーでしたら

MySQLのUnicodeColに日本語文字列をセットする問題の自分なりの解決

といった方法でうまくいくのかもしれません。が、僕はさくらの共有サーバーを使っているため、MySQLの設定ファイルなどを直接触ることができません。ODBC接続すらできません…。コメント欄にてデータベース作成時にutf8を指定すれば、という書き込みを頂いたのですがそれも試せそうにありません……。これはMySQLdbかDjango内部でどうにかするしかなさそうです。

Django | Model reference | Executing custom SQL

あたりを参考に、

connection.cursor().execute("SET CHARACTER SET utf8")

なんて送ってみても

OperationalError at /admin/sites/site/add/
(1115, "Unknown character set: 'utf8'")

また、MySQLdb/connection.py内にて文字コードを設定していると思しき変数charsetを'utf8'と固定にし、再インストールしてもエラーがでてしまう。

上のメッセージを読むとかなり嫌な予感がする訳ですが……MySQLのバージョンを調べてみる。

>>> import MySQLdb
>>> cnx = MySQLdb.connect(host='mysql**.db.sakura.ne.jp', user='******', passwd='******')
>>> cnx.get_server_info()
'4.0.27'

MySQL AB :: Unicode and Other Funny Characters :: What 4.0 Did

MySQL 4.0 (and earlier versions) only supported what amounted to a combined notion of the character set and collation with single-byte character encodings, which was specified at the server level. The default was latin1, which corresponds to a character set of latin1 and collation of latin1_swedish_ci in MySQL 4.1.

No information about the character set (and collation) of the data is stored in the table, and there is no support in MySQL 4.0 for converting between character sets. To convert the data to another character set, you would need to change the server's character set, and then use an external tool to convert the data.

A common strategy for applications built using MySQL 4.0 that needed to handle data in multi-byte character encodings (such as UTF-8) that were not supported natively was to simply store the data in VARCHAR fields (or TEXT, CHAR, etc). The application would either ignore the fact that MySQL would simply sort the data incorrectly for strings that weren't really in the character set that the server thought they were, or do the sorting in the application. That strategy worked fine, you just had to handle all of the encoding issues in your application, and you also lost the ability to take full advantage of MySQL's full-text searching.

MySQL4.0は文字コード間の変換をサポートしてないから、サーバー側で設定してね、と書いてあるように見える……。しかしUnicodeをUnicodeとして扱えないにしてもデータの挿入自体はできるようなので、そこでエラーが出るというのはやっぱりどこかがおかしいんでしょう。

結局、エンコードを適当に試した結果、下のようにすると正常にタイトルの取得と挿入ができました。エンコードを行う際にunicode()だとエラーが出ますが、encode('utf-8')だと大丈夫なようです。FeedParserで取得したタイトルの文字コードがutf-8じゃなかったということなんですかね。なんか動いてはいるんですが僕自身理解できてません。

image070411.jpg

from django.db import models
import feedparser

# Create your models here.
class Site(models.Model):
        title = models.CharField(blank=True, maxlength=100)
        rss = models.URLField(blank=False, verify_exists=True, maxlength=100)
        last = models.DateTimeField(auto_now_add=True)

        def save(self):
                if not self.title:
#                       self.title = unicode(feedparser.parse(self.rss).feed.title)
                        self.title = feedparser.parse(self.rss).feed.title
                        self.title = self.title.encode('utf-8')
                super(Site, self).save()

        def __str__(self):
                return self.rss
        class Admin:
                list_display = ('title', 'rss', 'last')

ありがちですが作ってみます。

流れとしては

プロジェクト作成

wwwの下に作ると閲覧者に設定ファイルなどが見えてしまうので避けたほうが良い。

$ django-admin.py startproject PROJECTNAME

アプリケーションの作成

上で作ったフォルダに移動して

$ python manage.py startapp APPLICATIONNAME

settings.pyに追加

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'PROJECTNAME.APPRICATIONNAME',
)

Model, View, Controllerをそれぞれ設計して、

$ python manage.py syncdb

作ってみる

必要最低限の機能を考えて、タイトル、RSSのURI、最終閲覧日を属性とすることにする。RSSは入力必須。タイトルは任意で、空欄であればRSSからタイトルを取得して埋める。最終閲覧日にはとりあえず登録日を入れておく。

from django.db import models

# Create your models here.
class Site(models.Model):
        title = models.CharField(blank=True, maxlength=100)
        rss = models.URLField(blank=False, verify_exists=True, maxlength=100)
        last = models.DateTimeField(auto_now_add=True)

        def __str__(self):
                return self.rss
        class Admin:
                list_display = ('title', 'rss', 'last')

FeedParser

RSSからタイトルを取得するには、FeedParserを使うと、

self.title = feedparser.parse(self.rss).feed.title

一発でタイトルが取得できる。

$ wget http://downloads.sourceforge.net/feedparser/feedparser-4.1.zip
$ unzip feedparser-4.1.zip
$ python setup.py install
$ mv feedparser.py /home/USERNAME/local/lib/python

さくらの場合共有ディレクトリにファイルを置こうとしてエラーが出るので、Djangoをインストールする際に作成したPYTHONPATHの通ったフォルダに置いておく。

save()をオーバーライド

入力に応じて処理を振り分けたいときには、save()をオーバーライドすればいいようだ。

from django.db import models
import feedparser

# Create your models here.
class Site(models.Model):
        title = models.CharField(blank=True, maxlength=100)
        rss = models.URLField(blank=False, verify_exists=True, maxlength=100)
        last = models.DateTimeField(auto_now_add=True)

        def save(self):
                if not self.title:
                        self.title = feedparser.parse(self.rss).feed.title
                models.Model.save(self)

        def __str__(self):
                return self.rss
        class Admin:
                list_display = ('title', 'rss', 'last')

これでいけるかと思ったがRSSから日本語の含まれるタイトルを取得したときエンコードエラーが出る。デフォルトのエンコーディングがASCIIになっているからなのか。

/home/USERNAME/local/lib/python/site-packages/sitecustomize.pyを作成し

import sys
sys.setdefaultencoding('utf-8')

これでも直らない。どうやらMySQLdbが悪さをしているようだ。解決方法としてはMySQLdbのソースを書き換えるか、sqlobject.dburiを正しく設定してやればいいらしい。ちょっと目が疲れたのでこれは明日。

あと入力されたURIがRSSのものでない場合にはエラー画面を出したい。これもやり方を調べなくては。

[Django][レンタルサーバー] さくらインターネットでDjango - oqreの日記

「さくらのレンタルサーバ」で Python 外部モジュールを使う - EmptyPage.jp

をほぼそのまま。

MySQLdbを入れようとしたところで、setuptoolsのバージョン0.6c5を使えとエラーが出たので、それだけ追加。

% wget http://cheeseshop.python.org/packages/source/s/setuptools/setuptools-0.6c5.tar.gz
% tar -zxf setuptools-0.6c5.tar.gz
% cd setuptools-0.6c5
% python setup.py install --prefix=$HOME/local

/www/PROJECTNAME/settings.pyを編集

DATABASE_ENGINE = 'mysql'
DATABASE_NAME = '*****'
DATABASE_USER = '*****'   
DATABASE_PASSWORD = '*******'
DATABASE_HOST = 'mysql**.db.sakura.ne.jp'
DATABASE_PORT = ''
...
TIME_ZONE = 'Japan'
...
LANGUAGE_CODE = 'ja'
...
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
)

データベースなんかがきちんと動いていれば、次のコマンドでスーパーユーザが作成される。

% python manage.py syncdb

シェルの起動

% python manage.py shell