Created
February 3, 2017 16:10
-
-
Save djowett/00edf2c2f2db2c6f538cc2613d60a070 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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