GAE Tips Vol.3

春寒ようやくぬるみ始めたこのごろ、皆さんいかがお過ごしでしょうか。
僕は、元気……うーん、まあ、元……気ではないですけど。

って言うか。

4月も終わるというのに寒すぎです。
夜とか。
日落ちると、けっこう肌寒いです。
今年は、春の訪れがあまり感じられません。そりゃ、桜とか咲いてたりしますから、春なんだなと思うことはありましたが、実感として体感として、それを感じることが少ないです。
まあ、長らく春の訪れたことのない僕の人生に比べればマシですが。
………………。
前文の解釈はご自由にどうぞ!


さて、お題はGoogle App Engineですね。
今回も適当に書き散らかします。


その前に、一つアナウンスを。
Nora's Ark
性懲りもなく、という感じですけれど。
一応、例の何にもないサイトよりは、いくらかマシなブログになってます。
ただの番宣ですみまませんが、一応この場を借りて紹介させていただきます。
見るか見ないかは、貴方しだいです。
見るか見ないか(の責任)は、貴方(の良心)しだいです。
※空白が気になるみんなは、ドラッグしてみよう!


いいとして。
今回は、宣伝したブログを作るに当って気になったことのまとめです。

1つのスクリプトファイルで複数のページを制御

まず、以下はあるapp.yamlの一部抜粋です。

-  url: /option
   script: script/option.py

-  url: /option/.*
   script: script/option.py

たとえば、ページの入力フォームで入力したテキストをデータストアに保存する、ちょうどGAEのチュートリアルにあるような機能をイメージしてください。

チュートリアルにおけるapp.yamlは、

- url: /.*
  script: helloworld.py

みたいになっていたかと思います。

なぜ、チュートリアルのように一まとめでなく、2段階に分けてあるのか。
たとえば、option.pyが、

<前略>

class OptionPage(webapp.RequestHandler):
   def get(self):
      ......
      path = os.path.join(os.path.dirname(__file__), 'option.html')
      self.response.out.write(template.render(path, template_values))

class OptionPagePost(webapp.RequestHandler):
   def post(self):
      ......
      self.redirect('/option')

class OptionDevPage(webapp.RequestHandler):
   def get(self):
      ......
      path = os.path.join(os.path.dirname(__file__), 'option-dev.html')
      self.response.out.write(template.render(path, template_values))

class OptionDevPagePost(webapp.RequestHandler):
   def post(self):
      ......
      self.redirect('/option-dev')

(中略)

applicationn = webapp.WSGIApplication(
                                     [('/option', OptionPage),
                                      ('/option/sign',OptionPagePost),
                                      ('/option-dev',OptionDevPage),
                                      ('/option-dev/sign',OptionDevPagePost)],
                                     debug=True)
(後略)

という構造になっているとしましょう。
ファイル構成は、

project/
  |-----option.py
  |-----option.html
  |-----option-dev.html

という具合です。

ちなみに、ここで出てくる〜/signというアドレスは、テンプレートとなるHTMLファイルにおける、

<form action="/option/sign" method="post">
  <div><textarea name="content" rows="3" cols="60"></textarea></div>
  <div><input type="submit" value="Add"></div>
</form>

の、formのaction部分に依存しています。ここを変えれば、signでなくとも構いませんが、スクリプトファイルとの整合性には注意が必要です。
※整合性がとれないと、アドレスが噛み合わずアクセスできなくなります。

本題に戻りましょう。

ここで、

-  url: /option
   script: script/option.py

-  url: /option/.*
   script: script/option.py

-  url: /option-dev/.*
   script: script/option.py

とすると、option-devというページが見つからないというエラーになります。


さて。
実を言うと、解決法が分かっただけで、原因はよく分かっていません。
て言うか、何一つ分かっていません。えっへん!
……いえ、嘘です。
エラーには、指定されたアドレスにマッチするURLが存在しません、みたいなことを言われました。
なので、おとなしく、

-  url: /option
   script: script/option.py

-  url: /option/.*
   script: script/option.py

-  url: /option-dev
   script: script/option.py

-  url: /option-dev/.*
   script: script/option.py

としたら、Not Foundエラーから解放されました。


予想としては、以前にもご紹介したスクリプトファイルと静的ファイルの格納場所の違いとか、ファイルの配置の問題、app.yamlの書き方ミスなどが考えられますが、僕自体が何一つ確かなものを持たないので、何とも言えません。
あるいは、全部僕が悪いのかもしれません。
ともあれ、1つのスクリプトファイルで複数のページを制御する際は、app.yamlが重要になってくることは念頭に置いておく必要がありそうです。
どこに原因があるかの見当がつくだけでも、バグの対処が少しは楽になりますからね。

自分のウェブページでDatastoreのデータ削除

実装経験があるのは、一括ではなく一個ずつです。
まあ、応用すれば一括指定ぐらいは楽にできると思います。
文章が走っているのは、眼が痛いからです。ドライアイが……。

前置きは置いといて。
今回ご紹介する方法では、数値IDを利用します。
とりあえず、参考にした公式ドキュメントです。
Google App Engine Ref. for Model


数値IDっていうのは、たとえば、チュートリアルのテンプレートを使った場合の例で出てくるHTMLファイルで、

    {% for greeting in greetings %}
      {% if greeting.author %}
        <b>{{ greeting.author.nickname }}</b> wrote:
      {% else %}
       An anonymous person wrote:
      {% endif %}
      <blockquote>{{ greeting.content|escape }}</blockquote>
    {% endfor %}

って部分がありますよね?このとき、

greeting.key.id

で得られるのが、数値IDです。
各データには、長ったらしい一意なキー値が割り当てられますが、数値IDは文字通り数字だけで表された、簡単なキー値です。


さて、上記のチュートリアルのコードをちょっと変えて、

    {% for greeting in greetings %}
      {% if greeting.author %}
        <b>{{ greeting.author.nickname }}</b> wrote:
      {% else %}
       An anonymous person wrote:
      {% endif %}
      {{ greeting.key.id }}
      <blockquote>{{ greeting.content|escape }}</blockquote>
    {% endfor %}

なんてすると、各書き込みの数値IDが表示されるようになります。
つまり、この数値を指定することで、該当する書き込みをDatastoreから削除できるというわけです。
そのステップを簡単に表すと、

  1. 数値IDを入力
  2. スクリプトで受け付ける
  3. 削除

です。


まず、受付と削除から。
これは新たに削除専用のクラスを作りました。

class Greetings(db.Model):
    ........

class OptionDel(webapp.RequestHandler):
    def post(self):
        greetings = Greetings()

        keys = self.request.get('keys')

        try:
            keys_int = int(keys)
            # Null message don't strage.
            if greetings.get_by_id(keys_int) != None:
                greetings.get_by_id(keys_int).delete()
        except ValueError:
            pass

        self.redirect('/option')

......

applicationn = webapp.WSGIApplication(
                                     [......
                                      ('/option/del',OptionDel)],
                                     debug=True)

もちろん、app.yamlは先述の通り、適切に書き記して置きます。(signがdelに変わるだけですが)
で。
try構文使ってるのは、数値以外の書き込みを弾くためです。
int()はstringをintに変換する関数ですが、数値になるような文字列以外を渡すとエラー吐いて、止まりやがります。なので、余計なものを無視する機構を組んだというわけです。
肝は、get_by_id()ですね。これで、数値IDで任意のデータを指定できます。


入力の方は、書き込みの応用ですね。

<form action="/option/del" method="post">
  <div><textarea name="keys" rows="1" cols="10"></textarea></div>
  <div><input type="submit" value="Delete"></div>
</form>

対応するHTMLファイルに、これを追記するだけです。


とまあ、以上です。
あっさりし過ぎですか?
僕なんかあんまりにもあんまりな頭の出来具合なので、この程度でも結構濃厚でしたけど。
でも、これで割とデータの管理がしやすくなりました。
宣伝したブログで実際に使ってますけど、書き込むの僕だけなので削除も気兼ねなくできますし。
応用が効くと言ったのは、つまり入力と受付を工夫する─たとえば、リストで受け付けるとかってすれば、一括指定も可能という意味です。
それは割とすぐ出来そうですね。

終わりに

することがないとだらけるし。
することがあると余計なことをする。
僕は自分がそんなどうしようもない人間らしき存在であることを、改めて認識しました。
いや、しなきゃいけないこと、結構あるんですよねー。(棒読み)
でもでも、そういう時ほど、別のことが捗っちゃったりしません?
掃除とかね。
それから、掃除とか。
あと……掃除とか。
………………。
忙しさにかまけて怠惰な私生活を送っていたつけが大きすぎました。
そんな訳で。


相変わらず、忙しい毎日ですが、それなりに元気にやってます。
まあ、空元気とも言いますが。
ではでは。
こんなヘタレブログの腐れ記事に目を通していただいて、ありがとうございました。
本日はこの辺で失礼します。