ブログにDjango1.2.5を組み込みました

日曜日, 2月 13th, 2011 by

Djangoをpython-blog-systemに組み込みました。もともと、Pythonのソースコード内で、self.response.out.writeを使ってHTMLを出力していたのですが、テンプレートエンジンを利用することで、Pythonで記述したロジック部とHTMLを分離できました。

変更前

変更前のソースコードです。汚いなんてもんじゃありません。MVCが渾然一体となっています。小規模なアプリであればこれでも良いと思いますが、これ以上大きなプロジェクトを開発するのであればMVCきっちり分けたほうがよいでしょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.api import memcache
import urllib, datetime, re
step = 10
title = "Python blog system"

class AuthHandler(webapp.RequestHandler):
  def get(self, key = ""):
    if users.get_current_user() == None:
      self.write("<a href="%s"?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0>Sign in or register</a>." % users.create_login_url("/admin"))
    elif users.is_current_user_admin() != True:
      self.write('Your account %s is not admin. <a href="%s"?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0>Log out</a> and log in with an admin account.' % (users.get_current_user(), users.create_logout_url("/admin")))
    else:
      if key:
        self.get2(key)
      else:
        self.get2()
  def post(self, key = ""):
    if users.is_current_user_admin():
      if key:
        self.post2(key)
      else:
        self.post2()
  def write(self, str):
    self.response.out.write(str)

class MainHandler(AuthHandler):
  def get(self, pageStr):
    try:
      page = int(pageStr)
    except ValueError:
      page = 0
    printHeader(self, title)
    self.write('<h1><a href="/?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">%s</a></h1>' % title)
    if users.is_current_user_admin():
      self.write('<h2>[<a href="/post/?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">New</a>] [<a href="%s"?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0>Log out</a>]</h2>'  % users.create_logout_url("/"))
    entries = Entry.all().order("-datetime")
    if not users.is_current_user_admin():
      entries = entries.filter("datetime <", datetime.datetime.now())
      entries = entries.filter("public =", True)
    entries = entries.fetch(step + 1, page * step)
    for entry in entries[:step]:
      printEntry(self, entry)
    if len(entries) > step:
      self.write('[ <a href="/%d?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0"> Next %d</a> ]' % (page + 1, step))
    if page > 0:
      self.write('[ <a href="/%d?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0"> Prev %d</a> ]' % (page - 1, step))
    printFooter(self)

class RSSHandler(AuthHandler):
  def get(self, pageStr):
    self.write(
u"""<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns="http://purl.org/rss/1.0/">
  <channel rdf:about="http://python-blog-system.appspot.com/">
    <title>%(title)s</title>
    <link>http://python-blog-system.appspot.com</link>
    <description>%(title)s</description>
    <dc:language>ja</dc:language>
    <dc:creator>N/A</dc:creator>
    <dc:date>%(now)s</dc:date>
  </channel>"""
% {'now' : '2010-12-31T16:57:12+09:00', 'title' : title})
    for entry in Entry.all().order("-datetime").filter("datetime <", datetime.datetime.now()).filter("public =", True).fetch(30):
      self.write("""  <item rdf:about="http://python-blog-system.appspot.com/entry/%(key)s">
    <title>%(title)s</title>
    <link>http://python-blog-system.appspot.com/entry/%(key)s</link>
    <description>%(body)s</description>
    <dc:date>%(datetime)s</dc:date>
  </item>
"""
% {"key" : entry.key(), "title" : h(entry.title), "body" : h(entry.body), "datetime" : entry.formattedDatetimeInJST})
    self.write("</rdf:RDF>")

class AdminHandler(AuthHandler):
  def get2(self):
    self.redirect("/")

class PostHandler(AuthHandler):
  def get2(self,key = ""):
    entry = Entry.get(key) if key != '' else Entry()
    title = "Edit Entry" if key!= '' else "New Entry"
    deleteButton = """<input type="button" value="delete" onclick="if(confirm('Are you sure to delete it?'))location.href='/delete/%s'"/>""" % key if key!= '' else ''
    if entry.public:
      public_radio = u"""<label><input type="radio" value="0" name="public">非公開</label><label><input type="radio" value="1" checked="checked" name="public">公開</label>"""
    else:
      public_radio = u"""<label><input type="radio" value="0" name="public" checked="checked">非公開</label><label><input type="radio" value="1" name="public">公開</label>"""
    printHeader(self, title)
    self.write("<h1>%s</h1>" % title)
    self.write(u"""<form method="post" action="/post/%(key)s"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" />
<h2>タイトル</h2><input type="text" name="title" value="%(title)s" style="width:400px"/>
<h2>本文</h2><textarea name="body" style="width:400px;height:300px;">%(body)s</textarea>
<h2>タグ</h2><input type="text" name="tags" value="%(tags)s" style="width:400px"/>
<h2>公開日時</h2><input type="text" name="datetime" value="%(datetime)s"style="width:400px;">
<h2>公開ステータス</h2>%(public_radio)s
<h2>画像 [<a href="/uploader?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0" target="_blank">uploader</a>]</h2>
<div>%(deleteButton)s<input type="submit" value="submit"/></div></form>"""
%
{"key" : key, "title" : entry.title, "body" : entry.body, "tags" : entry.tagStr(),"datetime" : (entry.datetime + datetime.timedelta(hours=9)).strftime('%Y-%m-%d %H:%M:%S'), "deleteButton":deleteButton, "public_radio": public_radio})
    printFooter(self)
  def post2(self, key = ""):
    if self.request.get("title") != '' and self.request.get("body") != '':
      entry = Entry.get(key) if key != '' else Entry()
      entry.title = self.request.get("title")
      entry.body = self.request.get("body")
      entry.datetime = datetime.datetime.strptime(self.request.get("datetime"), "%Y-%m-%d %H:%M:%S") - datetime.timedelta(hours=9)
      entry.public = self.request.get("public") == "1"
      entry.tags = []
      for tagStr in self.request.get('tags').replace(u' ',' ').replace('  ',' ').replace(',',' ').split(' '):
        tag = Tag.all().filter("tag =", tagStr).get()
        if tag == None:
          tag = Tag(tag = tagStr)
          tag.put()
        entry.tags.append(tag.key())
      entry.put()
    self.redirect('/')

class PostCommentHandler(AuthHandler):
  def post(self, key):
    if self.request.get("comment") != '':
      Comment(
        entry = Entry.get(key),
        comment = self.request.get("comment"),
        delpass = self.request.get("delpass"),
        nickname = self.request.get("nickname")
      ).put()
    self.redirect("/entry/%s" % key)

class DeleteHandler(AuthHandler):
  def get2(self, key):
    db.delete(Entry.get(key))
    self.redirect('/')

class DeleteCommentHandler(AuthHandler):
  def post(self, key):
    comment = Comment.get(key)
    entry_key = comment.entry.key()
    if self.request.get("delpass") == comment.delpass:
      db.delete(comment)
    self.redirect('/entry/%s' % entry_key)

class TagHandler(AuthHandler):
  def get(self, key):
    tagStr = urllib.unquote(key).decode('utf-8')
    title = "Python blog system / %s" % tagStr
    printHeader(self, title);
    tag = Tag.all().filter("tag =", tagStr).get()
    self.write('<h1><a href="/?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">Python blog system</a> / %s</h1>' % h(tagStr))
    if tag:
      entries = tag.entries
      entries = entries.filter("datetime <", datetime.datetime.now())
      entries = entries.filter("public =", True)
      for entry in entries:
        printEntry(self, entry)
    else:
      self.write("<h2>Tag %s does not exist</h2>" % h(tagStr))
    printFooter(self)

class EntryHandler(AuthHandler):
  def get(self, key):
    entry = Entry.get(key)
    printHeader(self, "%s / %s" % (title, entry.title));
    self.write('<h1><a href="/?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">%s</a></h1>' % title)
    printEntry(self, entry, commentDetail = True)
    printFooter(self)

class UploaderHandler(AuthHandler):
  def get2(self):
    printHeader(self, "Uploader")
    self.write("<h1>Uploader</h1>")
    for image in Image.all():
      self.write('<h2><img src="/image/%(key)s"/><br><input type="text" value="[img:%(key)s]" style="width:300px;font-size:x-small"/><input type="button" value="delete" onclick="location.href=\'/deleteImage/%(key)s\'"></h2>' % {"key":image.key()})
    self.write(u'<h2><form action="/uploader" enctype="multipart/form-data" method="post"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" /><input type="file" name="file"><input type="submit" value="Upload"></form></h2>')
    printFooter(self)
  def post2(self):
    if self.request.get('file'):
      image = Image()
      image.image = self.request.POST.get('file').file.read()
      image.contentType = self.request.body_file.vars['file'].headers['content-type']
      image.put()
    self.redirect('/uploader')

class DeleteImageHandler(AuthHandler):
  def get2(self, key):
    Image.get(key).delete()
    self.redirect('/uploader')

class ImageHandler(AuthHandler):
  def get(self, key):
    image = quickGet(key)
    self.response.headers['Content-Type'] = image.contentType.encode('utf-8')
    self.response.out.write(image.image)

class ImageHandler2(AuthHandler):
  def get(self, key):
    for i in range(20):
      start = datetime.datetime.now()
      quickGet(key)
      end = datetime.datetime.now()
      self.write("<div>%s</div>" % (end - start))
    self.write("<br>")
    for i in range(20):
      start = datetime.datetime.now()
      slowGet(key)
      end = datetime.datetime.now()
      self.write("<div>%s</div>" % (end - start))

def slowGet(key):
  return db.get(key)

def quickGet(key):
  data = memcache.get(key)
  if data == None:
    data = db.get(key)
    memcache.set(key = key, value = data, time=3600)
  return data

def printEntry(self, entry, commentDetail = False):
  if users.is_current_user_admin():
    editLink = '[<a href="/post/%s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">edit</a>]' % entry.key()
  else:
    editLink = ''
  hidden = ''
  if entry.datetime > datetime.datetime.now():
    hidden = " Not released yet "
  if entry.public == False:
    hidden = hidden + " Hidden "
  self.write('<div class="entry">\n<div class="entryHeader">\n')
  self.write('<h2 class="title"><a href="/entry/%(key)s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">%(title)s</a>%(hidden)s %(editLink)s</h2> <div class="entryDate">%(datetime)s</div>'
    % {"key" : entry.key(), "title" : h(entry.title), "editLink" : editLink, "datetime" : entry.formattedDatetimeInJST, "hidden":hidden})
  self.write('</div>\n')#header
  self.write(replaceImages(replaceStrongs(linkURLs(nl2br(h(entry.body))))));
  self.write('\n<div class="entryFooter">tag:\n')
  for tag in entry.tags:
    tagObj = Tag.get(tag)
    self.write('<a href="/tag/%s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0"><span class="tag">%s</span></a>\n' % (urllib.quote_plus(tagObj.tag.encode('utf-8')) ,h(tagObj.tag)))
  if commentDetail:
    self.write(u'<h2>コメント</h2><div class="comments"><a name="comments">\n')
    for comment in entry.comments.order('datetime'):
      if comment.nickname == None or comment.nickname == "":
        comment.nickname = "Anonymous"
      delbutton = u"""
        <div style="float:right">
          <form method="post" name="form" action="/deleteComment/%(key)s"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" />
          <input type="text" name="delpass" class="delcommentpassword"/>
          <input type="button" onclick="if(confirm('本当に削除しますか?'))form.submit()" value="削除" class="delcommentbutton"/>
          </form>
        </div><br clear="all">"""
% {"key":comment.key()}
      self.write('<h3 class="comment">%s: %s %s</h3>' % (comment.nickname, comment.comment, delbutton))
    self.write(u'<div style="width:84px;float:left;font-size:xx-small;position:relative;top:6px;">名前</div>')
    self.write(u'<div style="width:184px;float:left;font-size:xx-small;position:relative;top:6px;">コメント</div>')
    self.write(u'<div style="width:100px;float:left;font-size:xx-small;position:relative;top:6px;">削除パス</div>')
    self.write(u'<br clear="all">')
    self.write(u'<form action="/postComment/%s" method="post" style="padding:0"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" />' % entry.key())
    self.write(u'<input type="text" name="nickname" style="width:80px;">')
    self.write(u'<input type="text" name="comment" style="width:180px;">')
    self.write(u'<input type="text" name="delpass" style="width:50px;">')
    self.write(u'<input type="submit" value="投稿" style="width:50px">')
    self.write("</form></div>\n")
  else:
    self.write(u'<a href="/entry/%s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0#comments">コメント(%s)</a>\n' % (entry.key(), entry.comments.count()))
  self.write("</div>\n")#footer
  self.write("</div>\n")#entry

def urlReplacer(match, limit = 45):
  return '<a href="%s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0" target="_blank">%s</a>' % (match.group(), match.group()[:limit] + ('...' if len(match.group()) > limit else ''))

def linkURLs(str):
  return re.sub(r'([^"]|^)(https?|ftp)(://[\w:;/.?%#&=+-]+)', urlReplacer, str)

def replaceImages(str):
  return re.sub(r'\[img:(.*)\]', r'<img src="/image/\1" style="max-width:400px">', str)

def replaceStrongs(str):
  str = re.sub(r'\[strong\]', r'<strong>', str)
  str = re.sub(r'\[/strong\]', r'</strong>', str)
  return str

def printHeader(self, title):
  self.write("""<?xml version="1.0" encoding="UTF-8"?>
<!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>
  <link rel="alternate" type="application/rss+xml" title="RSS" href="rss"/>
  <meta http-equiv="content-script-type" content="text/javascript"/>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>%s</title>
  <link rel="stylesheet" type="text/css" href="/css/style.css"/>
  <meta name = "viewport" content = "width=420"/>
  <script type="text/javascript">
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXXX12-2']);
  _gaq.push(['_trackPageview']);
  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
  </script>"""
% (h(title)))
  self.write("</head>\n<body>\n");

def printFooter(self):
  self.write(u'<div>developed by <a href="http://php6.jp/python">python練習帳</a></div>\n</body>\n</html>')

def nl2br(str):
  return str.replace('\r\n','\n').replace('\n','<br />\n')

class Entry(db.Model):
  title = db.StringProperty(default = "")
  body = db.TextProperty(default = "")
  tags = db.ListProperty(db.Key)
  datetime = db.DateTimeProperty(auto_now_add = True)
  public = db.BooleanProperty(default = True)
  @property
  def formattedDatetimeInJST(self):
    return (self.datetime + datetime.timedelta(hours=9)).strftime("%Y-%m-%d %H:%M:%S")
  def tagStr(self):
    return " ".join([Tag.get(x).tag for x in self.tags])

class Tag(db.Model):
  tag = db.StringProperty()
  @property
  def entries(self):
    return Entry.all().filter('tags', self.key()).order('-datetime')

class Comment(db.Model):
  comment = db.TextProperty(default = "")
  entry = db.ReferenceProperty(Entry, collection_name = 'comments')
  user = db.UserProperty()
  datetime = db.DateTimeProperty(auto_now_add = True)
  delpass = db.TextProperty()
  nickname = db.TextProperty()

class Image(db.Model):
  image = db.BlobProperty()
  contentType = db.StringProperty()

def main():
  application = webapp.WSGIApplication([
    ('/tag/(.*)', TagHandler),
    ('/entry/(.*)', EntryHandler),
    ('/admin/?(.*)', AdminHandler),
    ('/postComment/?(.*)', PostCommentHandler),
    ('/post/?(.*)', PostHandler),
    ('/rss/?(.*)', RSSHandler),
    ('/deleteComment/?(.*)', DeleteCommentHandler),
    ('/deleteImage/(.*)', DeleteImageHandler),
    ('/delete/?(.*)', DeleteHandler),
    ('/uploader/?(.*)', UploaderHandler),
    ('/image/(.*)', ImageHandler),
    ('/simage/(.*)', ImageHandler2),
    ('/(.*)', MainHandler)
  ], debug=True)
  util.run_wsgi_app(application)

def h(html):
  return html.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')

if __name__ == '__main__':
  main()

変更後

Djangoのテンプレートエンジンを導入しました。変更前は1ファイルでしたが、変更後は7ファイルに増えました。

  • main.py
    プログラム本体です。
  • templates/base.html
    基本となるテンプレート
  • templates/index.html
    記事一覧と単独記事
  • templates/error.html
    エラー画面
  • templates/form.html
    投稿・編集フォーム
  • templates/upload.html
    添付ファイルアップロード
  • templates/rss.xml
    RSS

main.py

ソースコードからHTMLが無くなりました。行数も3割以上へりましたが、それ以上に見た目がすっきりしました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.dist import use_library
use_library('django', '1.2')
from google.appengine.ext.webapp import util, template
from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.api import memcache
import urllib, datetime, re, os
step = 10

class AuthHandler(webapp.RequestHandler):
  def get(self, key = ""):
    if users.get_current_user() == None:
      self.response.out.write("<a href="%s"?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0>Sign in or register</a>." % users.create_login_url("/admin"))
    elif users.is_current_user_admin() != True:
      self.response.out.write('Your account %s is not admin. <a href="%s"?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0>Log out</a> and log in with an admin account.' % (users.get_current_user(), users.create_logout_url("/admin")))
    else:
      if key:
        self.get2(key)
      else:
        self.get2()
  def post(self, key = ""):
    if users.is_current_user_admin():
      if key:
        self.post2(key)
      else:
        self.post2()

class MainHandler(AuthHandler):
  def get(self, pageStr):
    try:
      page = int(pageStr)
    except ValueError:
      page = 0
    entries = Entry.all().order("-datetime")
    if not users.is_current_user_admin():
      entries = entries.filter("datetime <", datetime.datetime.now())
      entries = entries.filter("public =", True)
    entries = entries.fetch(step + 1, page * step)
    params = {
      'entries': entries[:step],
      'login': users.is_current_user_admin(),
      'logout_url': users.create_logout_url("/"),
    }
    if len(entries) > step:
      params['next'] = page + 1
    if page > 0:
      params['prev'] = page - 1
    print_with_template(self, 'index.html', params)

class RSSHandler(AuthHandler):
  def get(self, pageStr):
    print_with_template(self, 'rss.xml', {'entries':Entry.all().order("-datetime").filter("datetime <", datetime.datetime.now()).filter("public =", True).fetch(30)})

class AdminHandler(AuthHandler):
  def get2(self):
    self.redirect("/")

class PostHandler(AuthHandler):
  def get2(self,key = ""):
    entry = Entry.get(key) if key != '' else Entry()
    print_with_template(self, 'form.html',{'entry':entry, 'key':key})
  def post2(self, key = ""):
    if self.request.get("title") != '' and self.request.get("body") != '':
      entry = Entry.get(key) if key != '' else Entry()
      entry.title = self.request.get("title")
      entry.body = self.request.get("body")
      entry.datetime = datetime.datetime.strptime(self.request.get("datetime"), "%Y-%m-%d %H:%M:%S") - datetime.timedelta(hours=9)
      entry.public = self.request.get("public") == "1"
      entry.tags = []
      for tagStr in self.request.get('tags').replace(u' ',' ').replace('  ',' ').replace(',',' ').split(' '):
        tag = Tag.all().filter("tag =", tagStr).get()
        if tag == None:
          tag = Tag(tag = tagStr)
          tag.put()
        entry.tags.append(tag.key())
      entry.put()
    self.redirect('/')

class PostCommentHandler(AuthHandler):
  def post(self, key):
    if self.request.get("comment") != '':
      Comment(
        entry = Entry.get(key),
        comment = self.request.get("comment"),
        delpass = self.request.get("delpass"),
        nickname = self.request.get("nickname")
      ).put()
    self.redirect("/entry/%s" % key)

class DeleteHandler(AuthHandler):
  def get2(self, key):
    db.delete(Entry.get(key))
    self.redirect('/')

class DeleteCommentHandler(AuthHandler):
  def post(self, key):
    comment = Comment.get(key)
    entry_key = comment.entry.key()
    if self.request.get("delpass") == comment.delpass:
      db.delete(comment)
    self.redirect('/entry/%s' % entry_key)

class TagHandler(AuthHandler):
  def get(self, key):
    tagStr = urllib.unquote(key).decode('utf-8')
    tag = Tag.all().filter("tag =", tagStr).get()
    if tag:
      entries = tag.entries
      entries = entries.filter("datetime <", datetime.datetime.now())
      entries = entries.filter("public =", True)
      params = {
        'entries': entries[:step],
        'login': users.is_current_user_admin(),
        'logout_url': users.create_logout_url("/"),
      }
      print_with_template(self, 'index.html', params)
    else:
      print_with_template(self, 'error.html', {'message':"Tag %s does not exist" % h(tagStr)})

class EntryHandler(AuthHandler):
  def get(self, key):
    print_with_template(self, 'index.html', {'entries':[Entry.get(key)], 'detail':True})

class UploaderHandler(AuthHandler):
  def get2(self):
    print_with_template(self, 'upload.html', {'images':Image.all()})
  def post2(self):
    if self.request.get('file'):
      image = Image()
      image.image = self.request.POST.get('file').file.read()
      image.contentType = self.request.body_file.vars['file'].headers['content-type']
      image.put()
    self.redirect('/uploader')

class DeleteImageHandler(AuthHandler):
  def get2(self, key):
    Image.get(key).delete()
    self.redirect('/uploader')

class ImageHandler(AuthHandler):
  def get(self, key):
    image = quickGet(key)
    self.response.headers['Content-Type'] = image.contentType.encode('utf-8')
    self.response.out.write(image.image)

def quickGet(key):
  data = memcache.get(key)
  if data == None:
    data = db.get(key)
    memcache.set(key = key, value = data, time=3600)
  return data

def urlReplacer(match, limit = 45):
  return '<a href="%s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0" target="_blank">%s</a>' % (match.group(), match.group()[:limit] + ('...' if len(match.group()) > limit else ''))

def linkURLs(str):
  return re.sub(r'([^"]|^)(https?|ftp)(://[\w:;/.?%#&=+-]+)', urlReplacer, str)

def replaceImages(str):
  return re.sub(r'\[img:(.*)\]', r'<img src="/image/\1" style="max-width:400px">', str)

def replaceStrongs(str):
  str = re.sub(r'\[strong\]', r'<strong>', str)
  str = re.sub(r'\[/strong\]', r'</strong>', str)
  return str

def nl2br(str):
  return str.replace('\r\n','\n').replace('\n','<br />\n')

def print_with_template(self, view, params = {}):
    fpath = os.path.join(os.path.dirname(__file__), 'templates', view)
    html = template.render(fpath, params)
    self.response.out.write(html)

## Models
class Entry(db.Model):
  title = db.StringProperty(default = "")
  body = db.TextProperty(default = "")
  tags = db.ListProperty(db.Key)
  datetime = db.DateTimeProperty(auto_now_add = True)
  public = db.BooleanProperty(default = True)
  @property
  def formattedDatetimeInJST(self):
    return (self.datetime + datetime.timedelta(hours=9)).strftime("%Y-%m-%d %H:%M:%S")
  def tagStr(self):
    return " ".join([Tag.get(x).tag for x in self.tags])
  def formatted_body(self):
    return replaceImages(replaceStrongs(linkURLs(nl2br(h(self.body)))))
  def tagList(self):
    return [Tag.get(t).tag for t in self.tags]
  def comment_count(self):
    return self.comments.count()

class Tag(db.Model):
  tag = db.StringProperty()
  @property
  def entries(self):
    return Entry.all().filter('tags', self.key()).order('-datetime')

class Comment(db.Model):
  comment = db.TextProperty(default = "")
  entry = db.ReferenceProperty(Entry, collection_name = 'comments')
  user = db.UserProperty()
  datetime = db.DateTimeProperty(auto_now_add = True)
  delpass = db.TextProperty()
  nickname = db.TextProperty()

class Image(db.Model):
  image = db.BlobProperty()
  contentType = db.StringProperty()

def main():
  application = webapp.WSGIApplication([
    ('/tag/(.*)', TagHandler),
    ('/entry/(.*)', EntryHandler),
    ('/admin/?(.*)', AdminHandler),
    ('/postComment/?(.*)', PostCommentHandler),
    ('/post/?(.*)', PostHandler),
    ('/rss/?(.*)', RSSHandler),
    ('/deleteComment/?(.*)', DeleteCommentHandler),
    ('/deleteImage/(.*)', DeleteImageHandler),
    ('/delete/?(.*)', DeleteHandler),
    ('/uploader/?(.*)', UploaderHandler),
    ('/image/(.*)', ImageHandler),
    ('/(.*)', MainHandler)
  ], debug=True)
  util.run_wsgi_app(application)

def h(html):
  return html.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')

if __name__ == '__main__':
  main()

base.html

基本となるテンプレート。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<?xml version="1.0" encoding="UTF-8"?>
<!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>
  <link rel="alternate" type="application/rss+xml" title="RSS" href="rss"/>
  <meta http-equiv="content-script-type" content="text/javascript"/>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>Python blog system</title>
  <link rel="stylesheet" type="text/css" href="/css/style.css"/>
  <meta name = "viewport" content = "width=420"/>
  <script type="text/javascript">
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXXX12-2']);
  _gaq.push(['_trackPageview']);
  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
  </script></head>
<body>
<div id="header">
{%block header%}
<h1><a href="/?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">Python Blog System</a></h1>
{%endblock header%}
</div>

<div id="middle">
<div id="contents">
{%block contents%}
{%if login%}
    <h2>[<a href="/post/?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">New</a>] [<a href="{{logout_url}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">Log out</a>]</h2>
{%endif%}
{%for entry in entries%}
<div class="entry">
    <div class="entryHeader">
        <h2 class="title">
            <a href="/entry/{{entry.key}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">{{entry.title}}</a>
    {%if login%}
            [<a href="/post/{{entry.key}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">edit</a>]
    {%endif%}
        </h2>
        <div class="entryDate">{{entry.formattedDatetimeInJST}}</div>
    </div>
    <div class="body">
        {{entry.formatted_body|safe}}
    </div>
    <div class="entryFooter">タグ:
    {%for tag in entry.tagList%}
        <a href="/tag/{{tag}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0"><span class="tag">{{tag}}</span></a>
    {%endfor%}
{%if detail%}
</div>
<h2>コメント</h2><div class="comments"><a name="comments">
    {%for comment in entry.comments%}
    <h3 class="comment">{{comment.nickname}}: {{comment.comment}}
        <div style="float:right">
          <form method="post" name="form" action="/deleteComment/{{comment.key}}"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" />
          <input type="text" name="delpass" class="delcommentpassword"/>
          <input type="button" onclick="if(confirm('本当に削除しますか?'))form.submit()" value="削除" class="delcommentbutton"/>
          </form>
        </div><br clear="all">
    </h3>
    {%endfor%}
    <div style="width:84px;float:left;font-size:xx-small;position:relative;top:6px;">名前</div>
    <div style="width:184px;float:left;font-size:xx-small;position:relative;top:6px;">コメント</div>
    <div style="width:100px;float:left;font-size:xx-small;position:relative;top:6px;">削除パス</div>
    <br clear="all">
    <form action="/postComment/{{entry.key}}" method="post" style="padding:0"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" />
    <input type="text" name="nickname" style="width:80px;">
    <input type="text" name="comment" style="width:180px;">
    <input type="text" name="delpass" style="width:50px;">
    <input type="submit" value="投稿" style="width:50px">
    </form></div>
{%else%}
    <a href="/entry/{{entry.key}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0#comments" style="float:right">コメント({{entry.comment_count}})</a>
</div>
{%endif%}

</div>
{%endfor%}
<div class="pager">
    {%if prev or prev == 0 %}
        [ <a href="/{{prev}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0"> Prev </a> ]
    {%endif%}
    {%if next%}
        [ <a href="/{{next}}?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0"> Next </a> ]
    {%endif%}
</div>
{%endblock contents%}

</div>
<div id="rightbar">
{%block rightbar%}

{%endblock rightbar%}
</div>
</div>
<div id="footer">
{%block footer%}
{%endblock footer%}
</div>
<div>developed by <a href="http://php6.jp/python">python練習帳</a></div></body></html>

index.html

記事一覧、単独記事で使っているテンプレート。今の所、何も拡張していません。

1
{% extends "base.html" %}

error.html

エラー表示画面。”base.html”のコンテンツ部分にエラーを表示します。

1
2
3
4
5
6
{% extends "base.html" %}

{%block contents%}
<h2>エラー</h2>
{{message}}
{%endblock contents%}

form.html

記事の投稿・編集フォーム。”base.html”のコンテンツ部分にフォームを表示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{% extends "base.html" %}

{%block contents%}
<form method="post" action="/post/{{key}}"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" />
<h2>タイトル</h2><input type="text" name="title" value="{{entry.title}}" style="width:400px"/>
<h2>本文</h2><textarea name="body" style="width:400px;height:300px;">{{entry.body}}</textarea>
<h2>タグ</h2><input type="text" name="tags" value="{{entry.tagStr}}" style="width:400px"/>
<h2>公開日時</h2><input type="text" name="datetime" value="{{entry.datetime|date:"Y-m-d H:i:s"}}"style="width:400px;">
<h2>公開ステータス</h2>
<label><input type="radio" value="0" name="public"{%if not entry.public%} checked="checked"{%endif%}>非公開</label>
<label><input type="radio" value="1" name="public"{%if entry.public%} checked="checked"{%endif%}>公開</label>
<h2>画像 [<a href="/uploader?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0" target="_blank">uploader</a>]</h2>
<div>
{%if key%}
<input type="button" value="delete" onclick="if(confirm('Are you sure to delete it?'))location.href='/delete/{{key}}'"/>
{%endif%}
<input type="submit" value="submit"/>
</div>
</form>
{%endblock contents%}

upload.html

ファイルアップロード画面。コンテンツ部分にアップロード済みファイルの一覧とアップロードフォームを表示します。

1
2
3
4
5
6
7
8
{% extends "base.html" %}

{%block contents%}
{%for image in images%}
    <h2><img src="/image/{{image.key}}"/><br><input type="text" value="[img:{{image.key}}]" style="width:300px;font-size:x-small"/><input type="button" value="delete" onclick="location.href='/deleteImage/{{image.key}}'"></h2>
{%endfor%}
<h2><form action="/uploader" enctype="multipart/form-data" method="post"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" /><input type="file" name="file"><input type="submit" value="Upload"></form></h2>
{%endblock contents%}

rss.xml

RSSを生成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns="http://purl.org/rss/1.0/">
  <channel rdf:about="http://python-blog-system.appspot.com/">
    <title>Python Blog System</title>
    <link>http://python-blog-system.appspot.com</link>
    <description>%(title)s</description>
    <dc:language>ja</dc:language>
    <dc:creator>N/A</dc:creator>
    <dc:date>%(now)s</dc:date>
  </channel>
{%for entry in entries%}
<item rdf:about="http://python-blog-system.appspot.com/entry/{{entry.key}}">
    <title>{{entry.title}}</title>
    <link>http://python-blog-system.appspot.com/entry/%(key)s</link>
    <description>{{entry.body}}</description>
    <dc:date>{{entry.formattedDatetimeInJST}}</dc:date>
  </item>
{%endfor%}
</rdf:RDF>

ソースコード(ダウンロード用)

python-blog-system
app.yamlの1行目のアプリケーション名を書き換えるだけで動作するはずです。きっと。

まとめ

Djangoのテンプレートエンジンを組み込むことで、MVCを分離し、見通しの良いソースコードに出来た。初めてのDjangoだったが、4時間程度の作業時間でMVCをきれいに分離できた。

Facebook comments:

comments

Leave a Reply


Get Adobe Flash player
single