Add basic image upload support for tempmon appliances

This commit is contained in:
Lance Edgar 2018-10-19 19:20:20 -05:00
parent 40a8761feb
commit 4aa8f43a7e
3 changed files with 94 additions and 0 deletions

View file

@ -78,6 +78,7 @@ requires = [
'paginate', # 0.5.6
'paginate_sqlalchemy', # 0.2.0
'passlib', # 1.7.1
'Pillow', # 5.3.0
'pyramid', # 1.3b2
'pyramid_beaker>=0.6', # 0.6.1
'pyramid_deform', # 0.2

View file

@ -105,6 +105,7 @@ class MasterView(View):
deleting = False
executing = False
has_pk_fields = False
has_image = False
row_attrs = {}
cell_attrs = {}
@ -854,6 +855,22 @@ class MasterView(View):
tools=self.make_row_grid_tools(instance))
return self.render_to_response('view', context)
def image(self):
"""
View which renders the object's image as a response.
"""
obj = self.get_instance()
image_bytes = self.get_image_bytes(obj)
if not image_bytes:
raise self.notfound()
# TODO: how to properly detect image type?
self.request.response.content_type = str('image/jpeg')
self.request.response.body = image_bytes
return self.request.response
def get_image_bytes(self, obj):
raise NotImplementedError
def clone(self):
"""
View for cloning an object's data into a new object.
@ -1433,7 +1450,9 @@ class MasterView(View):
return self.render_to_response('edit', context)
def save_edit_form(self, form):
uploads = self.normalize_uploads(form)
obj = self.objectify(form, self.form_deserialized)
self.process_uploads(obj, form, uploads)
self.after_edit(obj)
self.Session.flush()
@ -2977,6 +2996,12 @@ class MasterView(View):
config.add_view(cls, attr='view_version', route_name='{}.version'.format(route_prefix),
permission='{}.versions'.format(permission_prefix))
# image
if cls.has_image:
config.add_route('{}.image'.format(route_prefix), '{}/{{{}}}/image'.format(url_prefix, model_key))
config.add_view(cls, attr='image', route_name='{}.image'.format(route_prefix),
permission='{}.view'.format(permission_prefix))
# clone
if cls.cloneable:
config.add_tailbone_permission(permission_prefix, '{}.clone'.format(permission_prefix),

View file

@ -26,8 +26,14 @@ Views for tempmon appliances
from __future__ import unicode_literals, absolute_import
import os
import six
from PIL import Image
from rattail_tempmon.db import model as tempmon
import colander
from webhelpers2.html import HTML, tags
from tailbone.views.tempmon import MasterView
@ -42,6 +48,7 @@ class TempmonApplianceView(MasterView):
model_title_plural = "TempMon Appliances"
route_prefix = 'tempmon.appliances'
url_prefix = '/tempmon/appliances'
has_image = True
grid_columns = [
'name',
@ -53,15 +60,28 @@ class TempmonApplianceView(MasterView):
'location',
'clients',
'probes',
'image',
]
def configure_grid(self, g):
super(TempmonApplianceView, self).configure_grid(g)
g.set_sort_defaults('name')
g.set_link('name')
g.set_link('location')
def configure_form(self, f):
super(TempmonApplianceView, self).configure_form(f)
# name
f.set_validator('name', self.unique_name)
# image
if self.creating or self.editing:
f.set_type('image', 'file')
f.set_required('image', False)
else:
f.set_renderer('image', self.render_image)
# clients
if self.viewing:
f.set_renderer('clients', self.render_clients)
@ -74,6 +94,23 @@ class TempmonApplianceView(MasterView):
elif self.creating or self.editing:
f.remove_field('probes')
def unique_name(self, node, value):
query = self.Session.query(tempmon.Appliance)\
.filter(tempmon.Appliance.name == value)
if self.editing:
appliance = self.get_instance()
query = query.filter(tempmon.Appliance.uuid != appliance.uuid)
if query.count():
raise colander.Invalid(node, "Name must be unique")
def get_image_bytes(self, appliance):
return appliance.image_normal or appliance.image_raw
def render_image(self, appliance, field):
route_prefix = self.get_route_prefix()
url = self.request.route_url('{}.image'.format(route_prefix), uuid=appliance.uuid)
return tags.image(url, "Appliance Image", id='appliance-image') #, width=500) #, height=500)
def render_clients(self, appliance, field):
clients = {}
for probe in appliance.probes:
@ -88,6 +125,37 @@ class TempmonApplianceView(MasterView):
for client in clients]
return HTML.tag('ul', c=items)
def process_uploads(self, appliance, form, uploads):
image = uploads.pop('image', None)
if image:
# capture raw image as-is (note, this assumes jpeg)
with open(image['temp_path'], 'rb') as f:
appliance.image_raw = f.read()
# resize image and store as separate attributes
with open(image['temp_path'], 'rb') as f:
im = Image.open(f)
im.thumbnail((600, 600), Image.ANTIALIAS)
data = six.BytesIO()
im.save(data, 'JPEG')
appliance.image_normal = data.getvalue()
data.close()
im.thumbnail((150, 150), Image.ANTIALIAS)
data = six.BytesIO()
im.save(data, 'JPEG')
appliance.image_thumbnail = data.getvalue()
data.close()
# cleanup temp files
os.remove(image['temp_path'])
os.rmdir(image['tempdir'])
if uploads:
raise NotImplementedError("too many uploads?")
def includeme(config):
TempmonApplianceView.defaults(config)