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', # 0.5.6
|
||||||
'paginate_sqlalchemy', # 0.2.0
|
'paginate_sqlalchemy', # 0.2.0
|
||||||
'passlib', # 1.7.1
|
'passlib', # 1.7.1
|
||||||
|
'Pillow', # 5.3.0
|
||||||
'pyramid', # 1.3b2
|
'pyramid', # 1.3b2
|
||||||
'pyramid_beaker>=0.6', # 0.6.1
|
'pyramid_beaker>=0.6', # 0.6.1
|
||||||
'pyramid_deform', # 0.2
|
'pyramid_deform', # 0.2
|
||||||
|
|
|
@ -105,6 +105,7 @@ class MasterView(View):
|
||||||
deleting = False
|
deleting = False
|
||||||
executing = False
|
executing = False
|
||||||
has_pk_fields = False
|
has_pk_fields = False
|
||||||
|
has_image = False
|
||||||
|
|
||||||
row_attrs = {}
|
row_attrs = {}
|
||||||
cell_attrs = {}
|
cell_attrs = {}
|
||||||
|
@ -854,6 +855,22 @@ class MasterView(View):
|
||||||
tools=self.make_row_grid_tools(instance))
|
tools=self.make_row_grid_tools(instance))
|
||||||
return self.render_to_response('view', context)
|
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):
|
def clone(self):
|
||||||
"""
|
"""
|
||||||
View for cloning an object's data into a new object.
|
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)
|
return self.render_to_response('edit', context)
|
||||||
|
|
||||||
def save_edit_form(self, form):
|
def save_edit_form(self, form):
|
||||||
|
uploads = self.normalize_uploads(form)
|
||||||
obj = self.objectify(form, self.form_deserialized)
|
obj = self.objectify(form, self.form_deserialized)
|
||||||
|
self.process_uploads(obj, form, uploads)
|
||||||
self.after_edit(obj)
|
self.after_edit(obj)
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
|
|
||||||
|
@ -2977,6 +2996,12 @@ class MasterView(View):
|
||||||
config.add_view(cls, attr='view_version', route_name='{}.version'.format(route_prefix),
|
config.add_view(cls, attr='view_version', route_name='{}.version'.format(route_prefix),
|
||||||
permission='{}.versions'.format(permission_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
|
# clone
|
||||||
if cls.cloneable:
|
if cls.cloneable:
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.clone'.format(permission_prefix),
|
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
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import six
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from rattail_tempmon.db import model as tempmon
|
from rattail_tempmon.db import model as tempmon
|
||||||
|
|
||||||
|
import colander
|
||||||
from webhelpers2.html import HTML, tags
|
from webhelpers2.html import HTML, tags
|
||||||
|
|
||||||
from tailbone.views.tempmon import MasterView
|
from tailbone.views.tempmon import MasterView
|
||||||
|
@ -42,6 +48,7 @@ class TempmonApplianceView(MasterView):
|
||||||
model_title_plural = "TempMon Appliances"
|
model_title_plural = "TempMon Appliances"
|
||||||
route_prefix = 'tempmon.appliances'
|
route_prefix = 'tempmon.appliances'
|
||||||
url_prefix = '/tempmon/appliances'
|
url_prefix = '/tempmon/appliances'
|
||||||
|
has_image = True
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'name',
|
'name',
|
||||||
|
@ -53,15 +60,28 @@ class TempmonApplianceView(MasterView):
|
||||||
'location',
|
'location',
|
||||||
'clients',
|
'clients',
|
||||||
'probes',
|
'probes',
|
||||||
|
'image',
|
||||||
]
|
]
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
super(TempmonApplianceView, self).configure_grid(g)
|
super(TempmonApplianceView, self).configure_grid(g)
|
||||||
g.set_sort_defaults('name')
|
g.set_sort_defaults('name')
|
||||||
|
g.set_link('name')
|
||||||
|
g.set_link('location')
|
||||||
|
|
||||||
def configure_form(self, f):
|
def configure_form(self, f):
|
||||||
super(TempmonApplianceView, self).configure_form(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
|
# clients
|
||||||
if self.viewing:
|
if self.viewing:
|
||||||
f.set_renderer('clients', self.render_clients)
|
f.set_renderer('clients', self.render_clients)
|
||||||
|
@ -74,6 +94,23 @@ class TempmonApplianceView(MasterView):
|
||||||
elif self.creating or self.editing:
|
elif self.creating or self.editing:
|
||||||
f.remove_field('probes')
|
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):
|
def render_clients(self, appliance, field):
|
||||||
clients = {}
|
clients = {}
|
||||||
for probe in appliance.probes:
|
for probe in appliance.probes:
|
||||||
|
@ -88,6 +125,37 @@ class TempmonApplianceView(MasterView):
|
||||||
for client in clients]
|
for client in clients]
|
||||||
return HTML.tag('ul', c=items)
|
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):
|
def includeme(config):
|
||||||
TempmonApplianceView.defaults(config)
|
TempmonApplianceView.defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue