Skip to content

Instantly share code, notes, and snippets.

@djowett
Created February 3, 2017 16:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djowett/00edf2c2f2db2c6f538cc2613d60a070 to your computer and use it in GitHub Desktop.
Save djowett/00edf2c2f2db2c6f538cc2613d60a070 to your computer and use it in GitHub Desktop.
from five import grok
from plone.directives import dexterity, form
from zope import schema
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
from zope.interface import invariant, Invalid
from z3c.form import group, field
from plone.namedfile.interfaces import IImageScaleTraversable
from plone.namedfile.field import NamedImage, NamedFile
from plone.namedfile.field import NamedBlobImage as NamedBlobImageField
from plone.namedfile.file import NamedBlobImage
from plone.app.textfield import RichText
from z3c.relationfield.schema import RelationList, RelationChoice
from plone.formwidget.contenttree import ObjPathSourceBinder
from myproduct.contenttypes import MessageFactory as _
from zope.app.container.interfaces import IObjectAddedEvent
from zope.lifecycleevent.interfaces import IObjectModifiedEvent
from Acquisition import aq_base
from ZODB.POSException import ConflictError
from logging import exception
from cStringIO import StringIO
import plone.scale.scale
import PIL.Image
# Image size pre-rotation
PRE_ROT_HEIGHT=139
PRE_ROT_WIDTH=144
MAX_PORTRAIT_DIMENSIONS=471
QUALITY=90
# Interface class; used to define content-type schema.
class ISuccessStory(form.Schema, IImageScaleTraversable):
"""
A success story about a sponsored child.
This will be accompanied by a sqaure portrait image and a brief description
so that it can be advertised in a portlet
"""
title = schema.TextLine(
title=_(u"Story Title"),
description=_("For consistency please use a title like \"Victoria's Story\""),
)
description = schema.Text(
title=_(u"A short summary (20-30 words)"),
description=_("This will be shown in the Success Story portlet"),
)
portraitImage = NamedBlobImageField(
title=_(u"Portrait Image"),
description=_(u"A square portrait image at least %d x %d pixels "
u"which will be used in the Success Story portlet" %
(PRE_ROT_WIDTH, PRE_ROT_HEIGHT)),
)
text = RichText(
title=_(u"Story text. Include any other related images here"),
)
portletImage = NamedBlobImageField(
title=_(u"Portlet Image"),
description=_(u"An automatically resized and rotated version of the portrait image"),
readonly = True,
required = False,
)
# Custom content-type class; objects created for this content type will
# be instances of this class. Use this class to add content-type specific
# methods and properties. Put methods that are mainly useful for rendering
# in separate view classes.
class SuccessStory(dexterity.Item):
grok.implements(ISuccessStory)
# Add your class methods and properties here
def makePortletImage(self):
""" Automatically resize and rotate the portrait image and store it
in the portletImage field
"""
# From ImageScaling::create() in plone.namedfile.scaling
orig_value = getattr(self, 'portraitImage')
if orig_value is None:
return
if hasattr(aq_base(orig_value), 'open'):
orig_data = orig_value.open()
else:
orig_data = getattr(aq_base(orig_value), 'data', orig_value)
if not orig_data:
return
try:
# This block from internals of plone.scale.scale
if isinstance(orig_data, str):
orig_data=StringIO(orig_data)
image=PIL.Image.open(orig_data)
# Scale the image before rotating it
image = plone.scale.scale.scalePILImage(image, width=PRE_ROT_WIDTH,
height=PRE_ROT_HEIGHT, direction="down")
# Rotate the image 10 degrees anti-clockwise
rotatedImage=image.rotate(-10, expand=True)
#rotatedImage = image
if image.format=="PNG":
format="PNG"
else:
format="JPEG"
result=StringIO()
rotatedImage.save(result, format, quality=QUALITY, optimize=True)
result=result.getvalue()
mimetype = 'image/%s' % format.lower()
portlet_filename = u"%s-rotated.jpg" % unicode(self.id)
self.portletImage = NamedBlobImage(result, filename=portlet_filename)
except (ConflictError, KeyboardInterrupt):
raise
except Exception as e:
## We did our best, blank out the image
self.portletImage = None
exception('could not rotate portrait image "%r" of %r',
orig_value, self.absolute_url())
return
@grok.subscribe(ISuccessStory, IObjectModifiedEvent)
def storyAdded(story, event):
story.makePortletImage()
@grok.subscribe(ISuccessStory, IObjectAddedEvent)
def storyModified(story, event):
story.makePortletImage()
@form.validator(field=ISuccessStory['portraitImage'])
def validatePortraitImage(value):
"""Ensure image is large enough and (roughly) square
but not huge!
"""
if value._width < PRE_ROT_WIDTH or value._height < PRE_ROT_HEIGHT:
raise Invalid(_(u"The portrait image should be at least "
u"%d x %d pixels" % (PRE_ROT_WIDTH, PRE_ROT_HEIGHT)))
proportion = value._width / value._height
if proportion < 0.9 or proportion > 1.1:
raise Invalid(_(u"The portrait image should be square"))
if value._width > MAX_PORTRAIT_DIMENSIONS or value._height > MAX_PORTRAIT_DIMENSIONS:
raise Invalid(_(u"Your image is too big! Please supply an image no "
u"larger than %d x %d pixels" % (MAX_PORTRAIT_DIMENSIONS,
MAX_PORTRAIT_DIMENSIONS)))
# View class
# The view will automatically use a similarly named template in
# success_story_templates.
# Template filenames should be all lower case.
class View(grok.View):
grok.context(ISuccessStory)
grok.require('zope2.View')
# grok.name('view')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment