Question

I'm running Plone 4.3 and have been trying to create a custom portlet. The portlet is simple and should allow the user to upload an image and provide some descriptive text which can be displayed beneath the image.

Here's the code I have so far:

from zope.interface import implements
from zope.formlib import form
from zope import schema
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.portlets.portlets import base
from plone.memoize.instance import memoize
from plone.namedfile.field import NamedImage
from plone.directives import form
from z3c.form import field
from plone.app.portlets.browser import z3cformhelper

class IMyImagePortlet(form.Schema):

    myimagetitle = schema.TextLine(
        title=u"Image title",
        description=u"Enter the image title",
        default=u"",
        required=True)

    myimagedescription = schema.TextLine(
        title=u"Image text",
        description=u"Enter the text which appears below the image",
        default=u"",
        required=True)

    myimage = NamedImage(
        title=u"My image",
        description=u"Upload an image",
        required=True)

class Assignment(base.Assignment):
    implements(IMyImagePortlet)

    header = u"My Image"
    myimagetitle = u""
    myimagedescription = u""
    myimage = u""

    def __init__(self, myimagetitle=None, myimagedescription=None, myimage=None):
        self.myimagetitle = myimagetitle
        self.myimagedescription = myimagedescription
        self.myimage = myimage

    @property
    def title(self):
        """This property is used to give the title of the portlet in the
        "manage portlets" screen.
        """
        return u"My image"

class Renderer(base.Renderer):

    _template = ViewPageTemplateFile('templates/myimage_portlet.pt')

    def __init__(self, *args):
        base.Renderer.__init__(self, *args)

    @memoize
    def myimagetitle(self):
        return self.data.myimgaetitle

    @memoize
    def myimagedescription(self):
         return self.data.myimagedescription

    @memoize
    def myimage(self):
        return self.data.myimage

    def render(self):
        return self._template()

class AddForm(z3cformhelper.AddForm):
    fields = field.Fields(IMyImagePortlet)

    label = u"Edit image"
    description = u"A portlet which can display image with text"

    def create(self, data):
        return Assignment(**data)

class EditForm(z3cformhelper.EditForm):
    fields = field.Fields(IMyImagePortlet)

    label = u"Edit image"
    description = u"A portlet which can display image with text"

This seems to work as expected and allows me to add a new portlet with a title, image, and image description. However, I've been reading the docs and I couldn't find many examples of creating a portlet with an image upload field, so there may be a better way of doing this.

My issue is that I can't seem to render the image in the template. I've been referring to the docs at https://developer.plone.org/reference_manuals/external/plone.app.dexterity/advanced/files-and-images.html and have the following in my template, but the image is not being displayed:

<div tal:define="picture nocall:context/myimage"
     tal:condition="nocall:picture">
    <img tal:attributes="src string:${context/absolute_url}/@@download/myimage/${picture/filename};
                     height picture/_height | nothing;
                     width picture/_width | nothing;"
    />
</div>
Was it helpful?

Solution

We did exactly what you want. https://github.com/4teamwork/ftw.subsite/tree/master/ftw/subsite/portlets It's a teaser portlet, unfortunately not in a single package. but you can adapt the code to your needs.

We defined a browser view in image.py:

from Acquisition import aq_inner
from zope.publisher.browser import BrowserView


class ImageView(BrowserView):
    """View the image field of the image portlet. We steal header details
    from zope.app.file.browser.file and adapt it to use the dublin
    core implementation that the Image object here has."""

    def __call__(self):
        context = aq_inner(self.context)
        image = context.image
        self.request.response.setHeader('Content-Type', image.contentType)
        self.request.response.write(image.data)

Register with zcml (Important the image view is for your portlet Assignment):

<browser:page
    for=".yourportlet.Assignment"
    name="image"
    class=".image.ImageView"
    permission="zope.Public"
 />

In your portlet Renderer you have to define a method, which gets the image (traverse).

from plone.app.portlets.portlets import base


class MyPortletRenderer(base.Renderer)

    def image_tag(self):
        state = getMultiAdapter((self.context, self.request),
                                name="plone_portal_state")
        portal = state.portal()
        assignment_url = \
            portal.unrestrictedTraverse(
            self.data.assignment_context_path).absolute_url()

        return "<img src='%s/%s/@@image' alt=''/>" % (
            assignment_url,
            self.data.__name__)

Now you are able to use the image in your portlet:

<tal:image content="structure view/image_tag />

I don't know if there is a simpler solution. Naming could be better :-) But it works and it's tested https://github.com/4teamwork/ftw.subsite/blob/master/ftw/subsite/tests/test_subsiteportlet.py

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top