HOME > ブログ > Google App Engineでone-to-many(1対多)2kindとエンティティグループで

Google App Engineでone-to-many(1対多)2kindとエンティティグループで

「one-to-many(1対多)」最後のパターンはエンティティグループを使った親子関係で表現するやり方。
many(多)である子が「parent」を使って親を指定します。モデル的には他の「one-to-many(1対多)」のように何らかのプロパティを用意する必要がありません。





app.yaml
application: jinling-ren
version: 1
runtime: python
api_version: 1

handlers:
- url: .*
  script: main.py


main.py
#!/usr/bin/env python
#!-*- coding:utf-8 -*-
# one-to-manyサンプル
# http://www.jinlingren.com/

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import person


class Root(webapp.RequestHandler):
  def get(self):
    self.redirect('/person/list')


application = webapp.WSGIApplication([
  ('/', Root),
  ('/person/list', person.PersonList),
  ('/person/view/(\w+)', person.PersonView),
  ('/person/add', person.PersonAdd),
  ('/person/edit/(\w+)', person.PersonEdit),
  ('/person/delete/(\w+)', person.PersonDelete)
],debug=True)

def main():
  run_wsgi_app(application)


if __name__ == '__main__':
  main()


model.py
#!/usr/bin/env python
#!-*- coding:utf-8 -*-
# one-to-manyサンプル
# http://www.jinlingren.com/

from google.appengine.ext import db

class Person(db.Model):
  name = db.StringProperty(required=True)
  modified = db.DateTimeProperty(auto_now=True)
  created = db.DateTimeProperty(auto_now_add=True)

  def emails(self):
    return Email.all().ancestor(self).order('mailaddress')

class Email(db.Model):
  mailaddress = db.StringProperty(required=True)
  modified = db.DateTimeProperty(auto_now=True)
  created = db.DateTimeProperty(auto_now_add=True)


person.py
#!/usr/bin/env python
#!-*- coding:utf-8 -*-
# one-to-manyサンプル
# http://www.jinlingren.com/

from google.appengine.ext import webapp, db
from google.appengine.ext.webapp import template
import os
import cgi
import logging
from model import Person, Email

class PersonList(webapp.RequestHandler):
  def get(self):
    persons = Person.all().order('name')
    html = template.render(
      os.path.join(os.path.dirname(__file__), 'template', 'person', 'list.html'),
      {
        'persons': persons
      }
    )
    self.response.out.write(html)

class PersonView(webapp.RequestHandler):
  def get(self, key):
    person = db.get(db.Key(key))
    html = template.render(
      os.path.join(os.path.dirname(__file__), 'template', 'person', 'view.html'),
      {
        'person': person
      }
    )
    self.response.out.write(html)

class PersonAdd(webapp.RequestHandler):
  def get(self):
    html = template.render(
      os.path.join(os.path.dirname(__file__), 'template', 'person', 'add.html'),
      {}
    )
    self.response.out.write(html)
  def post(self):
    try:
      name = cgi.escape(self.request.get('name'))
      person = Person(name=name)
      person.put()
      mailaddress = cgi.escape(self.request.get('mailaddress'))
      if mailaddress:
        email = Email(parent=person, mailaddress=mailaddress)
        email.put()
      self.redirect('/person/list')
    except:
      self.redirect('/person/add')

class PersonEdit(webapp.RequestHandler):
  def get(self, key):
    person = db.get(db.Key(key))
    html = template.render(
      os.path.join(os.path.dirname(__file__), 'template', 'person', 'edit.html'),
      {
        'person': person
      }
    )
    self.response.out.write(html)

  def post(self, key):
    try:
      person = db.get(db.Key(key))
      person.name = cgi.escape(self.request.get('name'))
      person.put()
      for email in person.emails():
        post_mailaddress = cgi.escape(self.request.get('mailaddress_'+str(email.key())))
        if post_mailaddress:
          if email.mailaddress != post_mailaddress:
            email.mailaddress = post_mailaddress
            email.put()
        else:
          email.delete()
      mailaddress_new = cgi.escape(self.request.get('mailaddress_new'))
      if mailaddress_new:
        email = Email(parent=person, mailaddress=mailaddress_new)
        email.put()
      self.redirect('/person/list')
    except:
      self.redirect('/person/edit/'+key)

class PersonDelete(webapp.RequestHandler):
  def get(self, key):
    try:
      person = db.get(db.Key(key))
      if person:
        for email in person.emails():
          email.delete()
        person.delete()
      self.redirect('/person/list')
    except:
      self.redirect('/person/list')


template/person/list.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>one-to-manyサンプル</title>
</head>
<body>
<h1>PersonList(人物一覧)</h1>
> <a href="/person/add">人物追加</a><br><br>

<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
  <tr bgcolor="#EBEBEB">
    <td>&nbsp;</td>
    <td align="center">名前</td>
    <td align="center">メールアドレス</td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
  </tr>
{% for person in persons %}
  <tr bgcolor="#FFFFFF">
    <td><a href="/person/view/{{ person.key }}">詳細</a></td>
    <td>{{ person.name }}</td>
    <td><ul>{% for email in person.emails %}<li>{{ email.mailaddress }}</li>{% endfor %}</ul></td>
    <td><a href="/person/edit/{{ person.key }}">編集</a></td>
    <td><a href="/person/delete/{{ person.key }}" onClick="if(confirm('本当に削除しますか?')){return true;}else{return false;}">削除</a></td>
  </tr>
{% endfor %}
</table>

</body>
</html>


template/person/view.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>one-to-manyサンプル</title>
</head>
<body>
<h1>PersonView(人物参照)</h1>
> <a href="/person/list">人物一覧</a><br><br>

<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
  <tr>
    <td width="100" bgcolor="#EBEBEB">名前</td>
    <td width="250" bgcolor="#FFFFFF">{{ person.name }}</td>
  </tr>
  <tr>
    <td bgcolor="#EBEBEB">メールアドレス</td>
    <td bgcolor="#FFFFFF"><ul>{% for email in person.emails %}<li>{{ email.mailaddress }}</li>{% endfor %}</ul></td>
  </tr>
</table>

</body>
</html>


template/person/add.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>one-to-manyサンプル</title>
</head>
<body>
<h1>PersonAdd(人物追加)</h1>
> <a href="/person/list">人物一覧</a><br><br>

<form action="/person/add" method="post">
<table border="0" cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
  <tr>
    <td width="100" bgcolor="#EBEBEB">名前 <sup><font color="#FF0000">*</font></sup></td>
    <td width="250" bgcolor="#FFFFFF"><input type="text" name="name" value=""></td>
  </tr>
  <tr>
    <td bgcolor="#EBEBEB">メールアドレス</td>
    <td bgcolor="#FFFFFF"><input type="text" name="mailaddress" value=""></td>
  </tr>
</table><br>
<input type="submit" value="追加する">
</form>

<br>
<font color="#FF0000">*</font>は必須項目

</body>
</html>


template/person/edit.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>one-to-manyサンプル</title>
</head>
<body>
<h1>PersonEdit(人物編集)</h1>
> <a href="/person/list">人物一覧</a><br><br>

<form action="/person/edit/{{ person.key }}" method="post">
<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
  <tr>
    <td width="100" bgcolor="#EBEBEB">名前 <sup><font color="#FF0000">*</font></sup></td>
    <td width="250" bgcolor="#FFFFFF"><input type="text" name="name" value="{{ person.name }}"></td>
  </tr>
  <tr>
    <td bgcolor="#EBEBEB">メールアドレス</td>
    <td bgcolor="#FFFFFF">
{% for email in person.emails %}
    <input type="text" name="mailaddress_{{ email.key }}" value="{{ email.mailaddress }}"><br>
{% endfor %}
    <input type="text" name="mailaddress_new" value="">
    </td>
  </tr>
</table><br>
<input type="submit" value="変更する">
</form>

<br>
<font color="#FF0000">*</font>は必須項目

</body>
</html>


このparentの手法を使って「one-to-many(1対多)」を表現できるにはできるのだが、恐らくこの方法はこれを表現するために使うのはあまり適していないのではないかと思う。
データを入力してデータストアを除いてみると、モデルには作っていない「parent」という項目が作られそこには「ReferenceProperty」のようにキーがはいって関係性が作られている。
だからといって「ReferenceProperty」のようにコレクション名を使って逆参照できるというわけではない。なら普通に「ReferenceProperty」を使って表現する方がいいと思われるからだ。

ある有名なブログにも書かれているように、「Entity Groupはトランザクションのためにある」として必ずしも親子関係にあるからといって同じエンティティグループにいれる必要はなく、それは余計なことになり負荷にしかならない。
実際に自分で作ってみて「ReferenceProperty」に足らず、取得するにはデータストアに問合わせなくてはならないのなら、やはり「one-to-many(1対多)」を表現するためにこのparentを使ってやるというのは悪手なんじゃないかと思ってしまう。

| Google App Engine | Comment:0 |
コメント投稿












画像リロード
*半角の小英字、数字で構成されています