Add basic image upload support for tempmon appliances
This commit is contained in:
parent
40a8761feb
commit
4aa8f43a7e
1
setup.py
1
setup.py
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue