Relative URLs with Google App Engine and Jinja2 - google-app-engine

I use Jinja2 with Google App Engine. In am trying to use a relative path to load base.html with my edit.html template.
My theme directory structure looks like this...
application root
themes
default
admin
edit.html
base.html
I've tried this in the template...
{% extends "../base.html" %}
which returns this...
TemplateNotFound: ../base.html
When I do this...
{% extends "base.html" %}
my edit.html template loads, but without base.html.
How do I access base.html wich is one directory back from edit.html?
Now for the code...
app.yaml
application: myblog
version: 1
runtime: python27
api_version: 1
threadsafe: no
handlers:
- url: /admin/.*
script: admin.app
login: admin
- url: /favicon\.ico
static_files: favicon.ico
upload: favicon\.ico
- url: .*
script: static.app
builtins:
- remote_api: on
libraries:
- name: webapp2
version: "2.5.1"
- name: jinja2
version: latest
config.py
blog_name = 'My Blog'
theme = 'default'
post_path_format = '/%(year)d/%(month)02d/%(slug)s'
admin.py
def render_template(template_name, template_vals=None, theme=None):
template_path = os.path.join(os.path.dirname(__file__),
'themes', theme or config.theme)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_path))
template = env.get_template(template_name)
return template.render(**(template_vals or {}))
class BlogPost(db.Model):
#The URL path to the blog post. Posts have a path if they are published.
path = db.StringProperty()
title = db.StringProperty(required=True, indexed=False)
body = db.TextProperty(required=True)
published = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)
def render(self):
template_vals = {
'config': config,
'post': self,
}
return render_template("post.html", template_vals)
form = model_form(BlogPost, Form)
class PostForm(form):
pass
class PostHandler(webapp2.RequestHandler):
#webapp2.cached_property
def jinja2(self):
return jinja2.get_jinja2(app=self.app)
def render_to_response(
self, template_name, template_vals=None, theme=None):
template_name = os.path.join("admin", template_name)
self.response.out.write(render_template(
template_name, template_vals, theme))
def render_form(self, form):
self.render_to_response("edit.html", {'form': form})
def get(self):
self.render_form(PostForm())
app = webapp2.WSGIApplication([('/admin/newpost', PostHandler)],
debug=True)
edit.html
{% extends "../base.html" %}
{% block title %}Testing New Post Template{% endblock %}
{% block body %}
<form method="post" action="">
<table>
<div>{{ form.title.label }}: {{ form.title(class="css_class") }}</div>
{% if form.title.errors %}
<ul class="errors">{% for error in form.name.errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
<div>{{ form.body.label }}: {{ form.body() }}</div>
{% if form.body.errors %}
<ul class="errors">{% for error in form.body.errors %}<li>{{ error }}</li>{% endfor %}</ul>
{% endif %}
</table>
<input type="submit" />
</form>
{% endblock %}
Full Stack Trace
ERROR 2012-09-07 07:45:42,964 webapp2.py:1553] ../base.html
Traceback (most recent call last):
File "/home/john/google_projects/google_appengine/lib/webapp2/webapp2.py", line 1536, in __call__
rv = self.handle_exception(request, response, e)
File "/home/john/google_projects/google_appengine/lib/webapp2/webapp2.py", line 1530, in __call__
rv = self.router.dispatch(request, response)
File "/home/john/google_projects/google_appengine/lib/webapp2/webapp2.py", line 1278, in default_dispatcher
return route.handler_adapter(request, response)
File "/home/john/google_projects/google_appengine/lib/webapp2/webapp2.py", line 1102, in __call__
return handler.dispatch()
File "/home/john/google_projects/google_appengine/lib/webapp2/webapp2.py", line 572, in dispatch
return self.handle_exception(e, self.app.debug)
File "/home/john/google_projects/google_appengine/lib/webapp2/webapp2.py", line 570, in dispatch
return method(*args, **kwargs)
File "/home/john/google_projects/sprucepress/admin.py", line 86, in get
self.render_form(PostForm())
File "/home/john/google_projects/sprucepress/admin.py", line 83, in render_form
self.render_to_response("edit.html", {'form': form})
File "/home/john/google_projects/sprucepress/admin.py", line 80, in render_to_response
template_name, template_vals, theme))
File "/home/john/google_projects/sprucepress/admin.py", line 34, in render_template
return template.render(**(template_vals or {}))
File "/home/john/google_projects/google_appengine/lib/jinja2/jinja2/environment.py", line 894, in render
return self.environment.handle_exception(exc_info, True)
File "/home/john/google_projects/sprucepress/themes/default/admin/edit.html", line 1, in top-level template code
{% extends "../base.html" %}
TemplateNotFound: ../base.html

Well, I figured it out....
Here is what I learned...
Don't make your themes folder a static folder. This is a big no-no because on GAE static files are not available to the application. If Google App Engine is telling you the file is non accessible this may be the cause!
In Jinja2 all files are relative to the folder you assign to the jinja2 environment...not the template file! So, in my case... the "default" theme folder was my jinja environment's root folder. Instead of ../base.html I do base.html and it works because base.html is in the default folder that I told jinja2 to use to load templates.
Finally, if you want jinja2 to look for a file in multiple folders you can.

Related

List files from a static folder in FastAPI

I know how to serve static files in FastAPI using StaticFiles, how can I enable
directory listing like in Apache web server?
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI(...)
app.mount("/samples", StaticFiles(directory='samples'), name="samples")
# GET http://localhost:8000/samples/path/to/file.jpg -> OK
# GET http://localhost:8000/samples -> not found error
I am not sure FastAPI/Starlette exposes functionality to do that out of the box.
But it shouldn't be too hard to implement something yourself. This could be a starting point:
import os
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
#app.get("/static", response_class=HTMLResponse)
def list_files(request: Request):
files = os.listdir("./static")
files_paths = sorted([f"{request.url._url}/{f}" for f in files])
print(files_paths)
return templates.TemplateResponse(
"list_files.html", {"request": request, "files": files_paths}
)
list_files.html
<html>
<head>
<title>Files</title>
</head>
<body>
<h1>Files:</h1>
<ul>
{% for file in files %}
<li>{{file}}</li>
{% endfor %}
</ul>
</body>
</html>
When you hit /static you would see:
Additional notes
Note that list_files.html is an html template and should sit in the directory templates/ as #Michel Hua pertinently mentioned in his comment. For more info on templates check the templates docs.

Read File from GAE blobstore

Using the code below,
class MainHandler(webapp.RequestHandler):
def get(self):
upload_url = blobstore.create_upload_url('/upload')
self.response.out.write('<html><body>')
self.response.out.write('<form action="%s" method="POST" enctype="multipart/form-data">' % upload_url)
self.response.out.write("""Upload File: <input type="file" name="file"><br> <input type="submit"
name="submit" value="Submit"> </form></body></html>""")
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is file upload field in the form
blob_info = upload_files[0]
self.redirect('/serve/%s' % blob_info.key())
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
resource = str(urllib.unquote(resource))
blob_info = blobstore.BlobInfo.get(resource)
self.send_blob(blob_info)
app = webapp.WSGIApplication(
[('/', MainHandler),
('/upload', UploadHandler),
('/serve/([^/]+)?', ServeHandler),
], debug=True)
if __name__ == '__main__':
run_wsgi_app(app)
I was able to read files like .pdf,.txt, and media files from my blob store. But files like .doc,.docx returns files that are not readable.
I have tried using blob_reader but still did not work, how do I get to read files like .doc and .docx?
Your browser cannot handle doc files. But you can download the file and open the file with a viewer.
To download a blob, use:
self.send_blob(blob_info,save_as=True)
or:
self.send_blob(blob_info,save_as='amsdoc.docx')

I can not download a blob from GAE BlobStore

What is wrong with this code? I can not download a blob following the download url sent as response of the UploadHandler. I am getting a 404 response from the server.
I have doubts about how to send an url safe version of the blob key.
import urllib
import webapp2
from google.appengine.ext import blobstore
from google.appengine.ext.webapp import blobstore_handlers
MAIN = """<html>
<body>
<form action="%s" method="POST" enctype="multipart/form-data">
<p>Upload File:<input type="file" name="file"></p>
<p><input type="submit" name="submit" value="Submit">
</form>
</body>
</html>
"""
DOWNLOAD = """<html><body><p>%s</p></body></html>"""
class MainHandler(webapp2.RequestHandler):
def get(self):
upload_url = blobstore.create_upload_url('/upload')
self.response.out.write(MAIN % upload_url)
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
def post(self):
upload_files = self.get_uploads('file') # 'file' is name field in the form
blob_info = upload_files[0]
_key = blob_info.key()
_url = '/download/%s' % str(_key)
_url_text = blob_info.filename
self.response.out.write(DOWNLOAD % (_url, _url_text))
class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
blob_info = blobstore.Blob.get(resource)
self.sendblob(blob_info)
app = webapp2.WSGIApplication([('/', MainHandler),
('/upload', UploadHandler),
('/download/<resource>', DownloadHandler)],
debug=True)
app.yaml file is
application: georef
version: 1
runtime: python27
api_version: 1
threadsafe: false
libraries:
- name: webapp2
version: latest
handlers:
- url: /.*
script: georef.app
It looks like you copy and pasted the code wrong from the docs:
from google.appengine.ext import blobstore
from google.appengine.ext.webapp import blobstore_handlers
class ServeHandler(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
resource = str(urllib.unquote(resource))
blob_info = blobstore.BlobInfo.get(resource)
self.send_blob(blob_info)
You're missing the line to unencode the resource string in case it has any strange characters in it.

GAE upload image then dynamically serve the image

I'm trying to make a simple app on GAE that allows a user to enter a url to an image and a name. The app then uploads this image to the Datastore along with its name.
After the upload the page self redirects and then should send the image back to the client and display it on their machine.
After running all I get is a Server error. Since I am new to GAE please could someone tell me if my code is at least correct.
I can't see what is wrong with my code. (I have checked for correct indentation and whitespace). Below is the code:
The python:
import jinja2 # html template libary
import os
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
import urllib
import urllib2
import webapp2
from google.appengine.ext import db
from google.appengine.api import urlfetch
class Default_tiles(db.Model):
name = db.StringProperty()
image = db.BlobProperty(default=None)
class MainPage(webapp2.RequestHandler):
def get(self):
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render())
class Upload(webapp2.RequestHandler):
def post(self):
# get information from form post upload
image_url = self.request.get('image_url')
image_name = self.request.get('image_name')
# create database entry for uploaded image
default_tile = Default_tiles()
default_tile.name = image_name
default_tile.image = db.Blob(urlfetch.Fetch(image_url).content)
default_tile.put()
self.redirect('/?' + urllib.urlencode({'image_name': image_name}))
class Get_default_tile(webapp.RequestHandler):
def get(self):
name = self.request.get('image_name')
default_tile = get_default_tile(name)
self.response.headers['Content-Type'] = "image/png"
self.response.out.write(default_tile.image)
def get_default_tile(name):
result = db.GqlQuery("SELECT * FROM Default_tiles WHERE name = :1 LIMIT 1", name).fetch(1)
if (len(result) > 0):
return result[0]
else:
return None
app = webapp2.WSGIApplication([('/', MainPage),
('/upload', Upload),
('/default_tile_img', Get_default_tile)],
debug=True)
The HTML:
<html>
<head>
<link type="text/css" rel="stylesheet" href="/stylesheets/main.css" />
</head>
<body>
<form action="/upload" method="post">
<div>
<p>Name: </p>
<input name="image_name">
</div>
<div>
<p>URL: </p>
<input name="image_url">
</div>
<div><input type="submit" value="Upload Image"></div>
</form>
<img src="default_tile_img?{{ image_name }}">
</body>
</html>
Any help at all will be so much appreciated. Thanks you!
UPDATE
Thanks to Greg, I know know how to view error logs. As Greg said I was missing a comma, I have updated the code above.
The app now runs, but when I upload an image, no image shows on return. I get the following message in the log:
File "/Users/jamiefearon/Desktop/Development/My Programs/GAE Fully functional website with css, javascript and images/mywebsite.py", line 53, in get
default_tile = self.get_default_tile(name)
TypeError: get_default_tile() takes exactly 1 argument (2 given)
I only passed one argument to get_default_tile() why does it complain that I passed two?
You're missing a comma after ('/upload', Upload) in the WSGIApplication setup.
use this python code
import jinja2 # html template libary
import os
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
import urllib
import urllib2
import webapp2
from google.appengine.ext import db
from google.appengine.api import urlfetch
class Default_tiles(db.Model):
name = db.StringProperty()
image = db.BlobProperty(default=None)
class MainPage(webapp2.RequestHandler):
def get(self):
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render())
class Upload(webapp2.RequestHandler):
def post(self):
# get information from form post upload
image_url = self.request.get('image_url')
image_name = self.request.get('image_name')
# create database entry for uploaded image
default_tile = Default_tiles()
default_tile.name = image_name
default_tile.image = db.Blob(urlfetch.Fetch(image_url).content)
default_tile.put()
self.redirect('/?' + urllib.urlencode({'image_name': image_name}))
class Get_default_tile(webapp2.RequestHandler):
def get_default_tile(self, name):
result = db.GqlQuery("SELECT * FROM Default_tiles WHERE name = :1 LIMIT 1", name).fetch(1)
if (len(result) > 0):
return result[0]
else:
return None
def get(self):
name = self.request.get('image_name')
default_tile = self.get_default_tile(name)
self.response.headers['Content-Type'] = "image/png"
self.response.out.write(default_tile)
app = webapp2.WSGIApplication([('/', MainPage),
('/upload', Upload),
('/default_tile_img', Get_default_tile)],
debug=True)

TemplateNotFound(template) with Google App Engine & Jinja2

I'm getting a TemplateNotFound error on Google App Engine with Jinja2 (complete stack trace below.)
What I expect to see is the index.html with the "greet" variable passed to the index.html template file. What I don't understand is why I get the template not found error when the path to index.html in the TraceBack is correct.
What I've tried...
tried a relative path by taking out "os.path.dirname(file)" in template path.
using "template" instead of themes as a directory name.
Here is my code.
app.yaml
application: codemywayout
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /admin/.*
script: admin.app
login: admin
- url: /static/([^/]+)/(.*)
static_files: template/\1/static/\2
upload: static/.*
- url: /favicon\.ico
static_files: favicon.ico
upload: favicon\.ico
- url: .*
script: static.app
builtins:
- remote_api: on
libraries:
- name: webapp2
version: "2.5.1"
- name: jinja2
version: latest
admin.py
from google.appengine.ext import db
import webapp2
import jinja2
import os
import fix_path
import config
def render_template(template_name, template_vals=None, theme=None):
template_path = os.path.join(os.path.dirname(__file__) , \
"themes", theme or config.theme, template_name)
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_path))
return env.get_template(template_path, template_vals or {})
class BlogPost(db.Model):
title = db.StringProperty()
body = db.StringProperty()
def render(self):
template_vals = {
'config': config,
'post': self,
}
return render_template("post.html", template_vals)
class BaseHandler(webapp2.RequestHandler):
def render_to_response(self, template_name, \
template_vals=None, theme=None):
template_name = os.path.join("admin", template_name)
self.response.out.write(render_template(template_name,\
template_vals, theme))
class AdminHandler(BaseHandler):
def get(self):
greet = "hello"
template_vals = {
'greet': greet
}
self.render_to_response("index.html", template_vals)
config.py
# Name of the blog
blog_name = 'My Blog'
# Selects the theme to use. Theme names correspond to directories under
# the 'themes' directory, containing templates and static content.
theme = 'default'
# Defines the URL organization to use for blog postings. Valid substitutions:
# slug - the identifier for the post, derived from the title
# year - the year the post was published in
# month - the month the post was published in
# day - the day the post was published in
# URL Options
# post_path_format = '/%(year)d/%(month)02d/%(slug)s'
post_path_format = '/%(slug)s'
TraceBack
Traceback (most recent call last):
File "C:\Users\john\webdev\google\lib\webapp2\webapp2.py", line 1536, in __call__
rv = self.handle_exception(request, response, e)
File "C:\Users\john\webdev\google\lib\webapp2\webapp2.py", line 1530, in __call__
rv = self.router.dispatch(request, response)
File "C:\Users\john\webdev\google\lib\webapp2\webapp2.py", line 1278, in default_dispatcher
return route.handler_adapter(request, response)
File "C:\Users\john\webdev\google\lib\webapp2\webapp2.py", line 1102, in __call__
return handler.dispatch()
File "C:\Users\john\webdev\google\lib\webapp2\webapp2.py", line 572, in dispatch
return self.handle_exception(e, self.app.debug)
File "C:\Users\john\webdev\google\lib\webapp2\webapp2.py", line 570, in dispatch
return method(*args, **kwargs)
File "C:\Users\john\webdev\workspace\codemywayout\admin.py", line 49, in get
self.render_to_response("index.html", template_vals)
File "C:\Users\john\webdev\workspace\codemywayout\admin.py", line 34, in render_to_response
template_vals, theme))
File "C:\Users\john\webdev\workspace\codemywayout\admin.py", line 14, in render_template
return env.get_template(template_path, template_vals or {})
File "C:\Users\john\webdev\google\lib\jinja2\jinja2\environment.py", line 719, in get_template
return self._load_template(name, self.make_globals(globals))
File "C:\Users\john\webdev\google\lib\jinja2\jinja2\environment.py", line 693, in _load_template
template = self.loader.load(self, name, globals)
File "C:\Users\john\webdev\google\lib\jinja2\jinja2\loaders.py", line 115, in load
source, filename, uptodate = self.get_source(environment, name)
File "C:\Users\john\webdev\google\lib\jinja2\jinja2\loaders.py", line 162, in get_source
pieces = split_template_path(template)
File "C:\Users\john\webdev\google\lib\jinja2\jinja2\loaders.py", line 33, in split_template_path
raise TemplateNotFound(template)
TemplateNotFound: C:\Users\john\webdev\workspace\codemywayout\themes\default\admin\index.html
I hope your not trying to load the Jinja template from the same path as your static files
static_files: template/\1/static/\2
As that path is not accessible to your application.
I would try and log the path that Jinja is trying to load the template from to help you understand where it is trying to load the template.
If you look at C:\Users\john\webdev\google\lib\jinja2\jinja2\loaders.py line 33 you can probably set a pdb breakpoint import pdb; pdb.set_trace( ) and see what's going on.
I'm using the SDK source distribution on a Mac, but if your code is the same as what I see, it's:
def split_template_path(template):
"""Split a path into segments and perform a sanity check. If it detects
'..' in the path it will raise a `TemplateNotFound` error.
"""
pieces = []
for piece in template.split('/'):
if path.sep in piece \
or (path.altsep and path.altsep in piece) or \
piece == path.pardir:
raise TemplateNotFound(template)
elif piece and piece != '.':
pieces.append(piece)
return pieces
Note the for piece in template.split('/') where it splits on '/' instead of path.sep. I suspect that your input is a C:\Path\to\file.html in which case piece is the entire path, adn path.sep (\ on windows) is indeed in piece, which gives a TemplateNotFound error.
Instead of template_name = os.path.join("admin", template_name) you could try template_name = 'admin/%s' % template_name, don't include template_name in the template_path and pass template_name instead of template_path into env.get_template(template_name, template_vals or {}) as loaders.py line 162 indicates that it looks for template_name in template_path
Your code, modified but untested would look something like
from google.appengine.ext import db
import webapp2
import jinja2
import os
import fix_path
import config
def render_template(template_name, template_vals=None, theme=None):
template_path = os.path.join(os.path.dirname(__file__) , \
"themes", theme or config.theme)
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(template_path))
return env.get_template(template_name, template_vals or {})
class BlogPost(db.Model):
title = db.StringProperty()
body = db.StringProperty()
def render(self):
template_vals = {
'config': config,
'post': self,
}
return render_template("post.html", template_vals)
class BaseHandler(webapp2.RequestHandler):
def render_to_response(self, template_name, \
template_vals=None, theme=None):
template_name = "admin/%s" % template_name
self.response.out.write(render_template(template_name,\
template_vals, theme))
class AdminHandler(BaseHandler):
def get(self):
greet = "hello"
template_vals = {
'greet': greet
}
self.render_to_response("index.html", template_vals)

Resources