如何在 Google App Engine webapp2 留言簿教程中制作显示两个图像的 'permalink' 详细信息页面

How to make a 'permalink' detail page displaying two images in Google App Engine webapp2 guestbook tutorial

我正在关注 Google App Engine tutorial (here's their complete demo code on GitHub) 并且想:

  1. 允许 留言簿用户在张贴问候语时 除了 'avatar' 之外还上传额外的图像。这可以称为'other'
  2. 发布问候语后,将用户重定向到带有URL的问候语详细信息页面/greeting/numeric-id 而不是列出所有问候语的主页。
  3. 使用名为 detail.html

我无法理解:

A) 如何编写在发布问候语后调用的重定向代码,以便它重定向到 URL,如 /greeting/numeric-id.

B) 如何编写用户被重定向到的详细视图和模板页面,以便问候语 id显示图像

这是显示我想要做什么的图表:

这里是guestbook.py:

import os
import urllib
from google.appengine.api import images
from google.appengine.api import users
from google.appengine.ext import ndb
from google.appengine.ext.webapp import blobstore_handlers
from google.appengine.ext import blobstore
import jinja2
import webapp2

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

DEFAULT_GUESTBOOK_NAME = 'default_guestbook'

def guestbook_key(guestbook_name=None):
    """Constructs a Datastore key for a Guestbook entity with name."""
    return ndb.Key('Guestbook', guestbook_name or 'default_guestbook')

class Author(ndb.Model):
    """Sub model for representing an author."""
    identity = ndb.StringProperty(indexed=False)
    email = ndb.StringProperty(indexed=False)

class Greeting(ndb.Model):
    """A model for representing an individual Greeting entry."""
    author = ndb.StructuredProperty(Author)
    date = ndb.DateTimeProperty(auto_now_add=True)
    avatar = ndb.BlobProperty(indexed=False, required=True)
    other = ndb.BlobProperty(indexed=False, required=True)

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')

        greetings = Greeting.query(
            ancestor=guestbook_key(guestbook_name)) \
            .order(-Greeting.date) \
            .fetch(10)

        self.response.out.write("""
              <form action="/sign?%s"
                    enctype="multipart/form-data"
                    method="post">
                <label>Avatar:</label>
                <input type="file" name="avatar"/><br>
                <label>Other Image:</label>
                <input type="file" name="other"/><br>
                <input type="submit" value="Submit">
              </form>
            </body>
          </html>""" % (urllib.urlencode({'guestbook_name': guestbook_name})))

class Image(webapp2.RequestHandler):
    """ Handle image stored as blobs of bytes. 
        No idea how the template knows to select a particular one. """
    def get(self):
        avatar_greeting_key = ndb.Key(urlsafe=self.request.get('avatar_id'))
        other_greeting_key = ndb.Key(urlsafe=self.request.get('other_id'))
        avatar_greeting = avatar_greeting_key.get()
        other_greeting = other_greeting_key.get()
        if avatar_greeting.avatar:
            self.response.headers['Content-Type'] = 'image/png'
            self.response.out.write(avatar_greeting.avatar)
        elif other_greeting.other:
            self.response.headers['Content-Type'] = 'image/png'
            self.response.out.write(other_greeting.other)
        else:
            self.response.out.write('No image')

class Guestbook(webapp2.RequestHandler):
    def post(self):
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = Author(
                    identity=users.get_current_user().user_id(),
                    email=users.get_current_user().email())

        avatar = self.request.get('avatar')
        avatar = images.resize(avatar, 100, 100)  
        other = self.request.get('other')
        other = images.resize(other, 400, 300)  

        greeting.avatar = avatar
        greeting.other = other

        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/greeting/%d' % greeting.key.id())

class Detail(webapp2.RequestHandler):
    """ Individual greeting. """
    def get(self, *args, **kwargs):
        guestbook_name = self.request.get('guestbook_name', DEFAULT_GUESTBOOK_NAME)

        greeting = Greeting.get_by_id(args[0],
                                          parent=guestbook_key(guestbook_name))

        template_values = {
            'greeting': greeting,
        }

        template = JINJA_ENVIRONMENT.get_template('detail.html')
        self.response.write(template.render(template_values))


app = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/img', Image),
    ('/sign', Guestbook),
    ('/greeting/(\d+)', Detail),
    ], debug=True)

我的detail.html模板:

<!DOCTYPE html>
{% autoescape true %}
<html>
  <head>
    <title>Greeting {{ greeting.id }}</title>
  </head>
  <body>
    <h2>Greeting {{ greeting.id }}</h2>
        Avatar: <img src="/img?avatar_id={{ greeting.key.urlsafe() }}">
        <br>
        Other: <img src="/img?other_id={{ greeting.key.urlsafe() }}">
  </body>
</html>
{% endautoescape %}

我的app.yaml万一有用:

runtime: python27
api_version: 1
threadsafe: true

# Handlers match in order, put above the default handler.
handlers:
- url: /stylesheets
  static_dir: stylesheets

- url: /.*
  script: guestbook.app

libraries:
- name: webapp2
  version: latest
- name: jinja2
  version: latest

错误:

Traceback (most recent call last):
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1535, in __call__
    rv = self.handle_exception(request, response, e)
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1529, in __call__
    rv = self.router.dispatch(request, response)
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/Users/simon/Projects/guestbook/guestbook.py", line 111, in get
    self.response.write(template.render(template_values))
  File "/Users/simon/Projects/google-cloud-sdk/platform/google_appengine/lib/jinja2-2.6/jinja2/environment.py", line 894, in render
    return self.environment.handle_exception(exc_info, True)
  File "/Users/simon/Projects/guestbook/detail.html", line 9, in top-level template code
    Avatar: <img src="/img?avatar_id={{ greeting.key.urlsafe() }}">
UndefinedError: 'None' has no attribute 'key'

任何帮助,甚至更好的示例代码,将不胜感激。 GAE/webapp2 带有详细示例代码、列表视图和模板的博客教程会很棒,但也许 GAE BlobStore 的数据结构不适合博客?

更新: 如果我添加 Dan 的回答中贡献的 python 检查代码,我会得到 500 错误而不是堆栈跟踪,如果我尝试模板检查,我会得到一个空白的问候页面。我已经用我的完整代码和解释我正在尝试做的事情的图表更新了这个问题。

我将从 B:

开始

错误提示greeting值为None,导致jinja2在模板渲染时试图在{{ greeting.key.urlsafe() }}中扩展greeting.key时出现异常

一个选项是重新安排处理程序代码,以防止在不满足必要条件的情况下呈现该模板,也许是这样的:

    ...
    greeting = Greeting.get_by_id(args[0])

    if not greeting or not isinstance(greeting.key, ndb.Key):
        # can't render that template, invalid greeting.key.urlsafe()
        webapp2.abort(500)  
        return

    template_values = {
        'greeting': greeting,
    }

    template = JINJA_ENVIRONMENT.get_template('detail.html')
    self.response.write(template.render(template_values))
    ...

或者你可以用适当的检查来包装引用变量的模板区域(恕我直言,更丑陋、更难、更脆弱,不过 - python 对于这种逻辑比 jinja2 更好),类似的东西:

{% if greeting and greeting.key %}<img src="/img?avatar_img_id={{ greeting.key.urlsafe() }}">{% endif %}

现在到 A.

简而言之 - 这不是个好主意,主要是因为您尝试在 URL 中使用的数字 ID 不是唯一的 除了 [=] 下的问候语51=]相同的父实体!。这在某种程度上解释了为什么问候语无效导致 B 答案出现错误。

greeting = Greeting.get_by_id(args[0]) 将 return None 除非您还创建了一个问候语实体,其中包含您在 args[0] 中传递的 ID 并且 没有父级!

为了通过 ID 获取您创建的问候语:

    greeting = Greeting(parent=guestbook_key(guestbook_name))

您需要致电:

    greeting = Greeting.get_by_id(args[0],
                                  parent=guestbook_key(guestbook_name))

如果你想继续同一个方向,你也可以在问候 URL 中对 guestbook_name 进行编码,这样你也可以获得所需的父密钥,也许是沿途的一些东西这些行:

/guestbook/<guestbook_name>/greeting/<numeric-id>.

您还需要仔细查看图像处理。您存在架构不一致:在图表和模型中,您将头像和其他图像都附加到一个问候语实体,但在 Image 处理程序中,每个图像都附加到一个单独的问候语。处理程序也根本不映射到图像的 URL(根据体系结构决策,它还需要对定位适当图像所需的数据进行额外编码)。

恐怕你还有很多工作要做,直到你把整个事情都做起来,远远超过一个单一的 SO 问题。退后一步,重新考虑您的架构,将其拆分成更小的部分,一次专注于一个部分并让它继续下去。熟悉各个部分的技术后,您会感觉更好地一次解决整个问题。