Git Product home page Git Product logo

plone.namedfile's Introduction

Introduction

This package contains fields and wrapper objects for storing:

  • A file with a filename
  • An image with a filename

Blob-based and non-blob-based types are provided. The blob-based types require the ZODB3 package to be at version 3.8.1 or later, and BLOBs to be configured in zope.conf.

plone.supermodel handlers are registered.

See the usage.rst doctest for more details.

Source Code

Note: This packages is licensed under a BSD license. Please do not add dependencies on GPL code!

Contributors please read the document Process for Plone core's development

Sources are at the Plone code repository hosted at Github.

plone.namedfile's People

Contributors

ale-rt avatar balavec avatar datakurre avatar davisagli avatar do3cc avatar dobri1408 avatar esteele avatar gforcada avatar gotcha avatar hannosch avatar jensens avatar khink avatar loechel avatar lrowe avatar maethu avatar mamico avatar matthewwilkes avatar mauritsvanrees avatar miohtama avatar mliebischer avatar mrtango avatar optilude avatar pbauer avatar petschki avatar smcmahon avatar sneridagh avatar tdesvenain avatar thet avatar tisto avatar vangheem avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

plone.namedfile's Issues

Stable image scales use weak instead of strong caching without plone.app.imaging

This is a stable image scale with a unique id and can be strongly cached:

  • some-image/@@images/71292397-ba62-4ca0-a6d6-37707e151e35.jpeg

These are unstable image scales which may change when the image is edited, so should be weakly cached:

  • some-image/@@images/image
  • some-image/@@images/image/preview

What happens in various Plone versions?

  • Plone 4.3: all use strong caching.
  • Plone 5.2 Python 2.7 Dexterity Image with plone.app.imaging available: stable uid scales are strongly cached, others are weakly cached. Perfect!
  • Plone 5.2 Python 2.7 Dexterity Image without plone.app.imaging available: all are weakly cached.
  • Plone 5.2 Python 3 Dexterity Image: all are weakly cached.

We are only interested in fixing 5.2. Check it like this:

  • Use the Plone coredev branch 5.2 on Python 2.7.
  • Call bin/instance-archetypes fg so plone.app.imaging is available.
  • Create a standard Plone Site. So no Archetypes content, only dexterity.
  • In the caching control panel import one of the standard caching profile, and enable caching.
  • Create an Image.
  • On the network tab of your browser developer tools check what headers are given for the various scales. For the unstable ones you will see headers X-Cache-Operation: plone.app.caching.weakCaching and X-Cache-Rule: plone.content.file. For the stable uid scales you will see X-Cache-Operation: plone.app.caching.strongCaching and X-Cache-Rule: plone.stableResource.
  • Stop the instance and start it without Archetypes code: bin/instance fg.
  • Do not change anything, simply visit the same Plone Site and the same image, and check the network tab for the headers of scales. All use weak caching.

SVG images with lots of metadata may not be displayed (width/height of 1px)

I noticed that certain SVG images are not being displayed correctly in Plone (e.g., on some listing views or in the fullscreen view). Upon closer inspection, it became apparent that although the SVG images are included in the HTML source code, they have a width and height of only 1px, rendering them invisible.

Auswahl_482

I started investigating the issue and identified the root cause. My SVG files contain a significant amount of metadata at the beginning.
Auswahl_478

This can occur if, for example, you embed the raw diagram data into the SVG header on draw.io
Auswahl_479

plone.namedfile attempts to determine the dimensions of the image, but it reads only the first 1024 bytes of an SVG file. This may result in a truncated XML and lead to dimensions with a width and height of 1px.

Auswahl_483
Auswahl_484

The issue could be reproduced with both Plone 6.0.3 and 6.0.6.

Example SVG file with a header larger than 1024 bytes:
image_with_big_header

New image scale sizes break old-style srcsets

This is about the srcsets that were introduced in Plone 5 already, not the new stuff that we are working on.
When we add the srcset attribute for 2x/3x pixel density to an image tag, there is a check if the resulting width and height would be larger than the original. If this is the case, we do not include the srcset.
With the new scale sizes, height is often 65536. This number times 2 or 3 is always larger than the original image height, so the srcset information never gets added anymore. Well, maybe when you upload a high resolution image made by Hubble. ;-)

I can think of two ways to solve this:

  1. When the height is ridiculously high, give it the same value as the width and keep the current check.
  2. Completely remove the height check, and only look at the width.

I will make a PR for the second way.

(We could think about dropping this functionality in favour of the new way, but for now this would be an easy fix.)

Make picture variants more robust - Access Unauthorized

Boundary conditions:

  • Image has a workflow with a private state
  • Upload and insert the Image in the TinyMCE Editor in a Document or News Item
  • let the image state "private"

Call the Page as Anonymous:

  1. in Zeo Installaton:
  • if you are on the Client which has do the create and insert job above, a broken placeholder in the rendered HTML is visible
  • if you are on a Client which has not do the transformation for picture variants an error page is visible
  1. in a single instance installation
  • a broken placeholder in the rendered HTML is visible
  • restart the instance, call the page, an error page is visible
Traceback (most recent call last):
  File "/plone6/.buildout/eggs/cp38/plone.app.textfield-1.3.6-py3.8.egg/plone/app/textfield/transform.py", line 54, in __call__
    data = transforms.convertTo(
  File "/plone6/.buildout/eggs/cp38/Products.PortalTransforms-3.2.0-py3.8.egg/Products/PortalTransforms/TransformEngine.py", line 172, in convertTo
    result = transform.convert(orig, data, context=context,
  File "/plone6/.buildout/eggs/cp38/Products.PortalTransforms-3.2.0-py3.8.egg/Products/PortalTransforms/chain.py", line 49, in convert
    data = transform.convert(orig, data, **kwargs)
  File "/plone6/.buildout/eggs/cp38/Products.PortalTransforms-3.2.0-py3.8.egg/Products/PortalTransforms/Transform.py", line 202, in convert
    return self._v_transform.convert(*args, **kwargs)
  File "/plone6/.buildout/eggs/cp38/plone.outputfilters-5.0.0b2-py3.8.egg/plone/outputfilters/transforms/html_to_plone_outputfilters_html.py", line 32, in convert
    res = apply_filters(filters, orig)
  File "/plone6/.buildout/eggs/cp38/plone.outputfilters-5.0.0b2-py3.8.egg/plone/outputfilters/__init__.py", line 6, in apply_filters
    res = filter(data)
  File "/plone6/.buildout/eggs/cp38/plone.outputfilters-5.0.0b2-py3.8.egg/plone/outputfilters/filters/picture_variants.py", line 48, in __call__
    self.img2picturetag.create_picture_tag(sourceset, elem.attrs)
  File "/plone6/.buildout/eggs/cp38/plone.namedfile-6.0.0b4-py3.8.egg/plone/namedfile/picture.py", line 66, in create_picture_tag
    obj = self.resolve_uid_url(src)
  File "/plone6/.buildout/eggs/cp38/plone.namedfile-6.0.0b4-py3.8.egg/plone/namedfile/picture.py", line 133, in resolve_uid_url
    obj = uuidToObject(uid)
  File "/plone6/.buildout/eggs/cp38/plone.app.uuid-2.2.0-py3.8.egg/plone/app/uuid/utils.py", line 96, in uuidToObject
    return parent.restrictedTraverse(final_path)
  File "/plone6/.buildout/eggs/cp38/Zope-5.6-py3.8.egg/OFS/Traversable.py", line 364, in restrictedTraverse
    return self.unrestrictedTraverse(path, default, restricted=True)
  File "/plone6/.buildout/eggs/cp38/Zope-5.6-py3.8.egg/OFS/Traversable.py", line 296, in unrestrictedTraverse
    next = guarded_getattr(obj, name)
AccessControl.unauthorized.Unauthorized: You are not allowed to access 'testbild3.jpeg' in this context

Solution:
Better error handling, checks are needed in picture.py that an object exists or access is possible

Remove deprecated extras from extras_requires

These were already scheduled for removal in version 5, but are stil here in the latest tag (5.6.0): blobs, editor, marshaler, scales, supermodel. We should remove them But first they need to be removed from some core packages, in both Plone 5.2 and 6.0:

  • plone.app.contenttypes
  • plone.app.dexterity
  • plone.app.versioningbehavior

ImageScale vs ImageScaling

Context

I'm reading the excellent docs โœจ (thanks @plone/documentation-team ๐Ÿ’ฏ ) and I noticed that when talking about the srcset it does not explicitly mentions what's the actual python API method you should use (I will try to provide a fix for the docs later)...

Issue

So, I opened GitHub and looked at plone/namedfile/scaling.py where I found 2 classes:

  • ImageScale
  • ImageScaling

With almost exact docstrings:

  • view used for generating (and storing) image scales
  • view used for rendering image scales

Little quiz: which docstring belongs to which class? ๐Ÿ™ƒ

Anyway, they look way too similar to not merge them, deprecate one, or... ? ๐Ÿค”

<IFRAME SRC=pdf> causes download window to pop up on mobile browsers (Plone 6 at least)

Expected: the PDF is embedded or (in the case of some browsers) a page element displays an affordance to download the PDF (for the browsers that cannot embed PDFs).

I think the problem is here โ€” when the file is served using DisplayFile() it needs a Content-Disposition: inline header, as per https://stackoverflow.com/questions/48028036/iframe-src-file-pdf-now-downloading-rather-than-displaying

Code in question:

class DisplayFile(Download):
    """Display a file, via ../context/@@display-file/fieldname/filename

    Same as Download, however in this case we don't set the filename so the
    browser can decide to display the file instead.
    """

    # Make the configuration available on the class.
    # Then subclasses can override this.
    allowed_inline_mimetypes = ALLOWED_INLINE_MIMETYPES
    disallowed_inline_mimetypes = DISALLOWED_INLINE_MIMETYPES
    use_denylist = USE_DENYLIST

    def set_headers(self, file):
        if hasattr(file, "contentType"):
            mimetype = file.contentType
            if self.use_denylist:
                if mimetype in self.disallowed_inline_mimetypes:
                    # Let the Download view handle this.
                    return super(DisplayFile, self).set_headers(file)
            else:
                # Use the allowlist
                if mimetype not in self.allowed_inline_mimetypes:
                    # Let the Download view handle this.
                    return super(DisplayFile, self).set_headers(file)
        set_headers(file, self.request.response)

Provide picture tag name with configurable srcset media and sizes attributes

The browser views to generate image scales here in plone.namedfile can generate <img> tags with a srcset attribute for 2x and 3x versions of the very same image.

i.e.

<img 
    src="http://localhost:8080/Plone/bla/@@images/c062d71e-0b44-4592-a577-2641d4253fb5.jpeg"
    alt="Description"
    title="more description"
    height="800"
    width="1200"
    srcset="
        http://localhost:8080/Plone/bla/@@images/HASH-FOR-2x.jpeg 2x,
        http://localhost:8080/Plone/bla/@@images/HASH-FOR-3x.jpeg 3x" />

On the current project I'm working on we need much more advanced requirements, something like:

<picture>
  <source
      media="(min-width: 1024px)"
      sizes="600px"
      srcset="https://dummyimage.com/360x3:4 360w,
              https://dummyimage.com/560x3:4 560w,
              https://dummyimage.com/768x3:4 768w,
              https://dummyimage.com/960x3:4 960w,
              https://dummyimage.com/1240x3:4 1240w,
              https://dummyimage.com/1440x3:4 1440w,
              https://dummyimage.com/1680x3:4 1680w,
              https://dummyimage.com/1920x3:4 1920w">
  <source
      media="(min-width: 520px)"
      sizes="100vw"
      srcset="https://dummyimage.com/360x3:2 360w,
              https://dummyimage.com/560x3:2 560w,
              https://dummyimage.com/768x3:2 768w,
              https://dummyimage.com/960x3:2 960w,
              https://dummyimage.com/1240x3:2 1240w,
              https://dummyimage.com/1440x3:2 1440w,
              https://dummyimage.com/1680x3:2 1680w,
              https://dummyimage.com/1920x3:2 1920w">
  <source
      media="(min-width: 0)"
      sizes="100vw"
      srcset="https://dummyimage.com/360x3:4 360w,
              https://dummyimage.com/560x3:4 560w,
              https://dummyimage.com/768x3:4 768w,
              https://dummyimage.com/960x3:4 960w,
              https://dummyimage.com/1240x3:4 1240w,
              https://dummyimage.com/1440x3:4 1440w,
              https://dummyimage.com/1680x3:4 1680w,
              https://dummyimage.com/1920x3:4 1920w">
  <img
      class="CUSTOM-IMG-CSS-CLASS"
      sizes="(min-width: 1024px) 600px, 100vw"
      srcset="https://dummyimage.com/360x3:2 360w,
              https://dummyimage.com/560x3:2 560w,
              https://dummyimage.com/768x3:2 768w,
              https://dummyimage.com/960x3:2 960w,
              https://dummyimage.com/1240x3:2 1240w,
              https://dummyimage.com/1440x3:2 1440w,
              https://dummyimage.com/1680x3:2 1680w,
              https://dummyimage.com/1920x3:2 1920w"
      src="https://dummyimage.com/768x3:2"
      alt=""
      decoding="async"
      loading="lazy">
</picture>

i.e. given a few media queries (the media attribute on the source tags) that go hand in hand (I'm not totally sure about that though) with the sizes attribute (again on the source tag), generate a few scales (that might be a list of the usual scales names that we have already in Plone) and set for each of them a specific condition descriptor (as MDN calls them).

Put a fallback (the img tag on the example above) for ensuring that something gets displayed on that ~5% that does not support srcset.

Has anyone tried to do something this elaborated so far by (over)using the current plone.namedfile facilities? ๐Ÿค”

Picture tag broken when pointing to original image

I am upgrading a site from Plone 6.0.0a3 to 6.0.4.
We have a page with rich text that points to a full image, instead of to a scale, so @@images/image:

<img
 alt=""
 class="image-richtext image-inline"
 data-linktype="image"
 data-scale=""
 data-val="a980702c15584a8e8b4a64df0e6ddbdd"
 src="../resolveuid/a980702c15584a8e8b4a64df0e6ddbdd"
 data-mce-src="../resolveuid/a980702c15584a8e8b4a64df0e6ddbdd"
 data-mce-selected="1"
 width="168"
 height="28">

As you can see, this has no picture variant information yet. I guess you can't create html like this anymore in TinyMCE if you use the normal UI, but it can be in existing content, or when you directly edit the html code.
In an upgrade step I use from collective.exportimport.fix_html import fix_html_in_content_fields to update the image tags, and it becomes this:

<img
 alt=""
 class="image-richtext image-inline picture-variant-medium"
 data-linktype="image"
 data-picturevariant="medium"
 data-scale=""
 data-val="a980702c15584a8e8b4a64df0e6ddbdd"
 src="resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/image"
 data-mce-src="resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/image"
 data-mce-selected="1"
 width="168"
 height="28">

Both this and the original shows up fine when editing, but after saving the new text, the image does not show up. This is because plone.outputfilters transforms it, with the picture variants and resolveuid transforms. Result:

<picture>
 <source
 srcset="resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/teaser 600w,
         resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/preview 400w,
         resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/large 768w,
         resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/larger 1000w,
         resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/great 1200w">
 <img alt="" class="image-richtext image-inline picture-variant-medium" loading="lazy"
    src="resolveuid/a980702c15584a8e8b4a64df0e6ddbdd/@@images/teaser"
 width="168"
 height="28">
</picture>

As you can see, this has @@images/scale-name which does not work. Instead it should be @@images/image/scale-name.

Also, it still has resolveuid in there, instead of an absolute url, so something has gone wrong in the resolveuid transform as well.

Maybe exportimport needs to do something different, starting here or here, but it looks okay to me.

So I think a fix in plone.namedfile is needed. I have a patch and will make a PR.

Image scales cache does not work in tests

I recently updated from 2.0.9 to 3.0.11 in my buildout and noticed some test failures. I was able to identify the problem: when running tests, the image scales cache does not work.

The reason is in the following lines:

def modified(self):
"""Provide a callable to return the modification time of content
items, so stored image scales can be invalidated.
"""
context = aq_base(self.context)
date = DateTime(context._p_mtime)
return date.millis()

During tests, a content item may have the _p_mtime attribute set to None. So the code above is actually doing DateTime(None), which returns the current date/time. The result is every time a scale is requested it considers the cached value invalid, because the object is considered modified.

In the old version context.modified() was used instead of _p_mtime, and this method always returned a non-None value.

I wonder how to solve this problem. Maybe modified should return a default date in the past?

error with when tiff images is processed

  Module ZPublisher.Publish, line 127, in publish
  Module ZPublisher.BaseRequest, line 508, in traverse
  Module ZPublisher.BaseRequest, line 344, in traverseName
  Module plone.namedfile.scaling, line 292, in publishTraverse
  Module plone.namedfile.scaling, line 440, in scale
  Module plone.namedfile.scaling, line 459, in calculate_srcset
  Module plone.namedfile.scaling, line 376, in getImageSize
  Module plone.namedfile.file, line 427, in getImageSize
  Module plone.namedfile.utils, line 136, in getImageInfo
  Module plone.namedfile.utils.tiff_utils, line 70, in process_tiff
UnboundLocalError: local variable 'width' referenced before assignment

Warning "unclosed file <_io.FileIO name='/var/folders/_0/57n3vm9j04s63yq3twfxx9v00000gn/T/BUCvncbbzzi' mode='rb' closefd=True>"

When running plone.restapi performance tests and in production (Sentry logs) we see lots of those errors:

/Users/timo/.buildout/eggs/ZODB-5.5.1-py3.7.egg/ZODB/blob.py:339: ResourceWarning: unclosed file <_io.FileIO name='/var/folders/_0/57n3vm9j04s63yq3twfxx9v00000gn/T/BUCvncbbzzi' mode='rb' closefd=True>
  super(BlobFile, self).close()
ResourceWarning: Enable tracemalloc to get the object allocation traceback

This has been originally reported on the plone.restapi issue tracker: plone/plone.restapi#836

TypeError after update from Plone 5.1.5 to 5.2.2

After I have updated from Plone 5.1.5 to 5.2.2 an so from plone.namedfile-4.2.6 to plone.namedfile-5.4.0
I get a lot of TypeErrors

Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 162, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 359, in publish_module
  Module ZPublisher.WSGIPublisher, line 250, in publish
  Module ZPublisher.BaseRequest, line 518, in traverse
  Module ZPublisher.BaseRequest, line 341, in traverseName
  Module plone.namedfile.scaling, line 307, in publishTraverse
  Module plone.namedfile.scaling, line 450, in scale
  Module plone.scale.storage, line 182, in scale
  Module plone.scale.storage, line 127, in _modified_since
  Module plone.scale.storage, line 143, in modified_time
TypeError: modified() takes exactly 1 argument (2 given)

and images are not shown anymore.

If I locally change https://github.com/plone/plone.namedfile/blob/master/plone/namedfile/scaling.py#L439 to

storage = AnnotationStorage(self.context, self.modified)

images are working again. This reverts one line of this commit
c1bed2f#diff-03f29bbadcdf640341c9061de5a07dccL428

If I start with a new created database everything works as expected without this change.

I am not sure if this a problem with my database or a general problem. Did I miss to update another package? Maybe someone can help?

Broken links to full size image on new `images-test` view

The two links to image_view_fullscreen don't work (404) if not called on an image (plone.app.contenttypes.interfaces.IImage interface).

See commit ce07e8d

One can easily test that on the demo site.
I tried with a news : https://classic.demo.plone.org/en/demo/a-news-item/@@images-test

Make picture variants more robust

If a picture variant references a scale that does not exist, you get errors. See this issue with the following traceback:

2022-09-08 00:53:28,984 ERROR   [plone.app.textfield:91][waitress-0] Transform exception
Traceback (most recent call last):
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/plone.app.textfield-1.3.6-py3.10.egg/plone/app/textfield/transform.py", line 57, in __call__
    data = transforms.convertTo(
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/Products.PortalTransforms-3.2.0-py3.10.egg/Products/PortalTransforms/TransformEngine.py", line 172, in convertTo
    result = transform.convert(orig, data, context=context,
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/Products.PortalTransforms-3.2.0-py3.10.egg/Products/PortalTransforms/chain.py", line 49, in convert
    data = transform.convert(orig, data, **kwargs)
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/Products.PortalTransforms-3.2.0-py3.10.egg/Products/PortalTransforms/Transform.py", line 202, in convert
    return self._v_transform.convert(*args, **kwargs)
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/plone.outputfilters-5.0.0b1-py3.10.egg/plone/outputfilters/transforms/html_to_plone_outputfilters_html.py", line 33, in convert
    res = apply_filters(filters, orig)
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/plone.outputfilters-5.0.0b1-py3.10.egg/plone/outputfilters/__init__.py", line 7, in apply_filters
    res = filter(data)
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/plone.outputfilters-5.0.0b1-py3.10.egg/plone/outputfilters/filters/picture_variants.py", line 50, in __call__
    elem.replace_with(self.img2picturetag.create_picture_tag(sourceset, elem.attrs))
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/plone.namedfile-6.0.0b3-py3.10.egg/plone/namedfile/picture.py", line 93, in create_picture_tag
    scale_width = self.get_scale_width(scale)
  File "/home/user/Projects/Rudd-O.com/localserver/buildout-cache/eggs/cp310/plone.namedfile-6.0.0b3-py3.10.egg/plone/namedfile/picture.py", line 50, in get_scale_width
    return scale_info[0]
TypeError: 'NoneType' object is not subscriptable

It would be good if the picture variants are forgiving when a scale does not exist. A warning in the logs would be in order though, so an admin can see this and fix it.

Svg image not displaying

steps:
add a new image. Browse to a svg image file. click save.
expected;
the image is displayed
result:
image is not displayed, only a link with the file size.

Plone version: 5.1.2 soft released, ie plone.namedfile-4.2.4

remarks:
there are 2 problems:

  • when an exception is thrown by PIL library, file has begun to be read so the scale is saved with the beginning of file missing
  • modern browsers expect a mime type of svg+xml
    Suggested change:
--- scaling.py.ori	2018-04-09 16:41:02.121472000 +0000
+++ scaling.py	2018-04-11 13:33:23.624061095 +0000
@@ -245,7 +245,8 @@
             raise
         except IOError:
             if getattr(orig_value, 'contentType', '') == 'image/svg+xml':
-                result = orig_data.read(), 'SVG', (width, height)
+                orig_data.seek(0)
+                result = orig_data.read(), 'SVG+XML', (width, height)
             else:
                 logger.exception(
                     'Could not scale "{0!r}" of {1!r}'.format(

Full screen display does not work of course, but it would need to pull lxml in the picture so it's more complicated.

AttributeError: 'NoneType' object has no attribute 'rfind'

Plone 5.1/5.2

This error occurs when you create an object with image content over plone.restapi where you do not specify an explicit filename for the image. Neither Plone nor plone.restapi check the existence of a filename for image content. Not passing a filename leads to this error when trying to edit -> save the related content object.

Perhaps the condition should be an and condition

https://github.com/plone/plone.formwidget.namedfile/blob/master/plone/formwidget/namedfile/widget.py#L272

I am not sure about the overall impact or the overall importance of the filename value in general.

Traceback (innermost last):
  Module ZPublisher.Publish, line 138, in publish
  Module ZPublisher.mapply, line 77, in mapply
  Module ZPublisher.Publish, line 48, in call_object
  Module plone.z3cform.layout, line 63, in __call__
  Module plone.z3cform.layout, line 47, in update
  Module plone.dexterity.browser.edit, line 58, in update
  Module plone.z3cform.fieldsets.extensible, line 65, in update
  Module plone.z3cform.patch, line 30, in GroupForm_update
  Module z3c.form.group, line 141, in update
  Module z3c.form.group, line 52, in update
  Module z3c.form.group, line 48, in updateWidgets
  Module z3c.form.field, line 277, in update
  Module z3c.form.browser.text, line 36, in update
  Module z3c.form.browser.widget, line 171, in update
  Module Products.CMFPlone.patches.z3c_form, line 47, in _wrapped
  Module z3c.form.widget, line 88, in update
  Module plone.formwidget.namedfile.widget, line 273, in extract
  Module plone.namedfile.utils, line 42, in safe_basename
AttributeError: 'NoneType' object has no attribute 'rfind'

Bad exif data prevents image upload

I have an image with apparently bad Exif data. Uploading this image in Plone 5.1.4 goes wrong. With a pdb in place:

WARNING plone.namedfile.utils cannot convert argument to integer
> /Users/maurits/shared-eggs/cp27m/plone.namedfile-4.2.5-py2.7.egg/plone/namedfile/utils/__init__.py(274)rotate_image()
-> raise
(Pdb) l
266          try:
267              exif_bytes = piexif.dump(exif_data)
268          except Exception as e:
269              log.warn(e)
270              try:
271                  del(exif_data['Exif'][piexif.ExifIFD.SceneType])
272              except KeyError as ke:
273                  import pdb; pdb.set_trace()
274  ->                raise
275              # This Element piexif.ExifIFD.SceneType cause error on dump
276              exif_bytes = piexif.dump(exif_data)
277          output_image_data = StringIO()
278          img.save(output_image_data, format=fmt, exif=exif_bytes)
279          width, height = img.size
(Pdb) exif_data.keys()
['Exif', '0th', 'Interop', '1st', 'thumbnail', 'GPSโ€™]
(Pdb) piexif.ExifIFD.SceneType
41729
(Pdb) sorted(exif_data['Exif'].keys())
[33434, 33437, 34850, 34855, 36864, 36867, 36868, 37121, 37377, 37378, 37380, 37383, 37385, 37386, 37500, 37510, 37520, 37521, 37522, 40960, 40961, 40962, 40963, 40965, 41486, 41487, 41488, 41985, 41986, 41987, 41990]

So in line 367, piexif.dump(exif_data) throws an exception:

Traceback (most recent call last):
  File "/Users/maurits/shared-eggs/cp27m/plone.namedfile-4.2.5-py2.7.egg/plone/namedfile/utils/__init__.py", line 267, in rotate_image
    exif_bytes = piexif.dump(exif_data)
  File "/Users/maurits/shared-eggs/cp27m/piexif-1.0.13-py2.7.egg/piexif/_dump.py", line 74, in dump
    gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length)
  File "/Users/maurits/shared-eggs/cp27m/piexif-1.0.13-py2.7.egg/piexif/_dump.py", line 337, in _dict_to_bytes
    offset)
  File "/Users/maurits/shared-eggs/cp27m/piexif-1.0.13-py2.7.egg/piexif/_dump.py", line 193, in _value_to_bytes
    value_str = (_pack_byte(*raw_value) +
  File "/Users/maurits/shared-eggs/cp27m/piexif-1.0.13-py2.7.egg/piexif/_dump.py", line 162, in _pack_byte
    return struct.pack("B" * len(args), *args)
error: cannot convert argument to integer

plone.namedfile catches this exception and turns it into a warning.
It then tries to fix the Exif data by deleting the SceneType key. Apparently that key caused a problem in earlier testing. But that key is not there in my image, so it throws a KeyError.
In fact, only when I delete all keys from exif_data[โ€˜Exifโ€™] can I call piexif.dump(exif_data) without getting an error.

In my particular use case, this is an image that was uploaded without problems to a Plone 4.3 site, and that I am importing with transmogrifier into Plone 5.1. It is not an issue with transmogrifier: simply uploading the image already throws the error.

I would propose that in NamedBlobImage.__init__ we catch all exceptions thrown by rotate_image and ignore the Exif data. That is the only place where the function is called.
With that change, the full image actually shows up rotated just fine, contrary to how it shows in Plone 4.3. That may depend on the browser. The scales show up fine in both versions.

Note: the Exif handling was added in plone.namedfile 4.1.1. The error should be there in Plone 5.1 and 5.2. I will create PRs.

AttributeError: SimpleViewClass object has no attribute 'Title'

Context is a tile, with this in the template:

<figure class="image-product"
  tal:define="scale view/@@images;
             img_tag python:scale.scale('image', scale='large').tag(css_class='img-responsive')">
  <img tal:replace="structure img_tag" />
</figure>

We can't ask for its Title:

Traceback (most recent call last):
  File "/Users/maurits/shared-eggs/cp39/plone.subrequest-1.9.3-py3.9.egg/plone/subrequest/__init__.py", line 154, in subrequest
    result = mapply(
  File "/Users/maurits/shared-eggs/cp39/Zope-5.5.1-py3.9.egg/ZPublisher/mapply.py", line 87, in mapply
    return object(*args)
  File "/Users/maurits/shared-eggs/cp39/plone.tiles-2.3.1-py3.9.egg/plone/tiles/tile.py", line 91, in __call__
    return self.index(*args, **kwargs)
  File "/Users/maurits/shared-eggs/cp39/Zope-5.5.1-py3.9.egg/Products/Five/browser/pagetemplatefile.py", line 126, in __call__
    return self.__func__(__self__, *args, **kw)
  File "/Users/maurits/shared-eggs/cp39/Zope-5.5.1-py3.9.egg/Products/Five/browser/pagetemplatefile.py", line 58, in __call__
    s = self.pt_render(
  File "/Users/maurits/shared-eggs/cp39/zope.pagetemplate-4.6.0-py3.9.egg/zope/pagetemplate/pagetemplate.py", line 133, in pt_render
    return self._v_program(
  File "/Users/maurits/shared-eggs/cp39/Zope-5.5.1-py3.9.egg/Products/PageTemplates/engine.py", line 365, in __call__
    return template.render(**kwargs)
  File "/Users/maurits/shared-eggs/cp39/z3c.pt-3.3.1-py3.9.egg/z3c/pt/pagetemplate.py", line 176, in render
    return base_renderer(**context)
  File "/Users/maurits/shared-eggs/cp39/Chameleon-3.9.1-py3.9.egg/chameleon/zpt/template.py", line 302, in render
    return super(PageTemplate, self).render(**_kw)
  File "/Users/maurits/shared-eggs/cp39/Chameleon-3.9.1-py3.9.egg/chameleon/template.py", line 215, in render
    raise_with_traceback(exc, tb)
  File "/Users/maurits/shared-eggs/cp39/Chameleon-3.9.1-py3.9.egg/chameleon/utils.py", line 53, in raise_with_traceback
    raise exc
  File "/Users/maurits/shared-eggs/cp39/Chameleon-3.9.1-py3.9.egg/chameleon/template.py", line 192, in render
    self._render(stream, econtext, rcontext)
  File "/Users/maurits/clients/zeelandia/plone60/var/cache/7d6e2bead38b2ed63866fc425e453f46.py", line 184, in render
    __value = _static_4624538736('python', "scale.scale('image', scale='large').tag(css_class='img-responsive')", econtext=econtext)(_static_4624538448(econtext, __zt_tmp))
  File "/Users/maurits/shared-eggs/cp39/zope.tales-5.1-py3.9.egg/zope/tales/pythonexpr.py", line 73, in __call__
    return eval(self._code, vars)
  File "<string>", line 1, in <module>
  File "/Users/maurits/clients/zeelandia/plone60/develop/plone.namedfile/plone/namedfile/scaling.py", line 126, in tag
    return ""
AttributeError: 'SimpleViewClass from /Users/maurits/clients/zeelan' object has no attribute 'Title'

Edge case with content with id "image"

There is a very specific edge case, where an item with id "image" will throw an exception. Steps to reproduce:

  1. Create a folder with any name (e.g. Test Folder), this results in /Plone/test-folder
  2. Inside this new folder, add another folder with any name (e.g. Images) this results in /Plone/test-folder/images
  3. Inside this subfolder, create another item, can be a page or another folder, and name it pecifically "Image" so its id ends up being "image". this results in /Plone/test-folder/images/image
  4. Go back to the folder created in step 1 (/Plone/test-folder)

The following traceback is observed:

Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 391, in publish_module
  Module ZPublisher.WSGIPublisher, line 285, in publish
  Module ZPublisher.mapply, line 98, in mapply
  Module ZPublisher.WSGIPublisher, line 68, in call_object
  Module zope.browserpage.simpleviewclass, line 44, in __call__
  Module Products.Five.browser.pagetemplatefile, line 126, in __call__
  Module Products.Five.browser.pagetemplatefile, line 58, in __call__
  Module zope.pagetemplate.pagetemplate, line 134, in pt_render
  Module Products.PageTemplates.engine, line 365, in __call__
  Module z3c.pt.pagetemplate, line 174, in render
  Module chameleon.zpt.template, line 331, in render
  Module chameleon.template, line 217, in render
  Module chameleon.utils, line 20, in raise_with_traceback
  Module chameleon.template, line 193, in render
  Module 3cb10457e85649fa285944d63a54ffc6, line 1869, in render
  Module 45cb177dc1ec34106adbaf082ff1c6b3, line 930, in render_master
  Module 45cb177dc1ec34106adbaf082ff1c6b3, line 1553, in render_content
  Module 3cb10457e85649fa285944d63a54ffc6, line 1854, in __fill_content_core
  Module 3cb10457e85649fa285944d63a54ffc6, line 121, in render_content_core
  Module 3cb10457e85649fa285944d63a54ffc6, line 492, in render_listing
  Module 3cb10457e85649fa285944d63a54ffc6, line 959, in render_entries
  Module zope.tales.pythonexpr, line 73, in __call__
   - __traceback_info__: (image_scale.tag(item, 'image', scale=thumb_scale_list, css_class=img_class, loading='lazy'))
  Module <string>, line 1, in <module>
  Module plone.memoize.volatile, line 73, in replacement
  Module plone.namedfile.scaling, line 774, in tag
  Module plone.namedfile.scaling, line 685, in tag
  Module plone.namedfile.scaling, line 610, in scale
  Module plone.scale.storage, line 219, in pre_scale
  Module plone.scale.storage, line 204, in hash_key
  Module plone.scale.storage, line 163, in modified_time
  Module plone.namedfile.scaling, line 567, in modified
  Module DateTime.DateTime, line 446, in __init__
DateTime.interfaces.SyntaxError: DateTime.interfaces.SyntaxError: Unable to parse (<bound method DexterityContent.modified of <Document at image>>,), {}

 - Expression: "python:image_scale.tag(item, 'image', scale=thumb_scale_list, css_class=img_class, loading='lazy')"
 - Filename:   ... .egg/plone/app/contenttypes/browser/templates/listing.pt
 - Location:   (line 164: col 56)
 - Source:     ... python:image_scale.tag(item, 'image', scale=thumb_scale_list, css_class=img_class, loading='lazy') ...
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Expression: "provider:plone.abovecontentbody"
 - Filename:   ... one/Products/CMFPlone/browser/templates/main_template.pt
 - Location:   (line 107: col 74)
 - Source:     ... ontent="structure provider:plone.abovecontentbody" />
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Expression: "context/@@main_template/macros/master"
 - Filename:   ... .egg/plone/app/contenttypes/browser/templates/listing.pt
 - Location:   (line 6: col 23)
 - Source:     ... tal:use-macro="context/@@main_template/macros/master"
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Arguments:  template: <Products.Five.browser.pagetemplatefile.ViewPageTemplateFile object at 0x7f542fcc2380>
               options: {}
               args: ()
               nothing: None
               modules: <Products.PageTemplates.ZRPythonExpr._SecureModuleImporter object at 0x7f5433d71cc0>
               request: <WSGIRequest, URL=http://localhost:8080/Plone/test-folder/listing_view>
               view: <Products.Five.browser.metaconfigure.SimpleViewClass from /trabajo/plone/buildout.coredev/eggs/plone.app.contenttypes-3.0.5-py3.10.egg/plone/app/contenttypes/browser/templates/listing.pt object at 0x7f542c0b34f0>
               context: <Folder at /Plone/test-folder>
               views: <Products.Five.browser.pagetemplatefile.ViewMapper object at 0x7f542c0b0580>
               here: <Folder at /Plone/test-folder>
               container: <Folder at /Plone/test-folder>
               root: <Application at >
               traverse_subpath: []
               user: <PropertiedUser 'admin'>
               default: <DEFAULT>
               repeat: <Products.PageTemplates.engine.RepeatDictWrapper object at 0x7f54266c0d00>
               loop: {'item': <Products.PageTemplates.engine.RepeatItem object at 0x7f5426b8f730>}
               target_language: None
               translate: <function BaseTemplate.render.<locals>.translate at 0x7f54266aac20>
               macroname: 'master'
               attrs: {}

The issue happens here

if fieldname is not None:
field = getattr(context, fieldname, None)
modified = getattr(field, "modified", None)
date = DateTime(modified or context._p_mtime)
where getattr(context, fieldname, None) returns the content item with id image instead of the expected field

I am not sure what the proper fix would be here... maybe ImageScaling should adapt some more specific interface than ITraversable ?

Suggestions?

Index all image scales, no matter the actual size

I find inconsistent that we index in the catalog metadata just some scales for an image and then requesting to the plone.restapi endpoint receiving all URLs for all configured image scales.

For instance:

This is the restapi response:

https://2023.ploneconf.org/++api++/accomodation/1111_fotpe1_fachada.jpg

"image": {
    "content-type": "image/jpeg", 
    "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-519-d8e3ae51320189e0d48d5032325bbab6.jpeg", 
    "filename": "1111_fotpe1_fachada.jpg", 
    "height": 600, 
    "scales": {
      "great": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-1200-0cb50e0c06f8f905ba111647b2512c29.jpeg", 
        "height": 600, 
        "width": 519
      }, 
      "huge": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-1600-f6c90960c0303e65aa57fbeb576533c0.jpeg", 
        "height": 600, 
        "width": 519
      }, 
      "icon": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-32-2e770277beaee989c15c5f64f550fad9.jpeg", 
        "height": 32, 
        "width": 28
      }, 
      "large": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-800-a56e1fb18d700a641285a81c30caca1e.jpeg", 
        "height": 600, 
        "width": 519
      }, 
      "larger": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-1000-71d3c8f46dbf7bd654a7d2aa0128fcfc.jpeg", 
        "height": 600, 
        "width": 519
      }, 
      "mini": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-200-f878c801f90e108d8236b98261b3262d.jpeg", 
        "height": 231, 
        "width": 200
      }, 
      "preview": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-400-c14554d35bfd7d8672bdca38d85c7ec8.jpeg", 
        "height": 462, 
        "width": 400
      }, 
      "teaser": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-600-102e1380a97eefef487b31a2e2dd48de.jpeg", 
        "height": 600, 
        "width": 519
      }, 
      "thumb": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-128-0cc82a203ab39309932391cc0e7945cd.jpeg", 
        "height": 128, 
        "width": 111
      }, 
      "tile": {
        "download": "https://2023.ploneconf.org/accomodation/1111_fotpe1_fachada.jpg/@@images/image-64-1cee74d235d28d0da7f4789e71b2f29d.jpeg", 
        "height": 64, 
        "width": 56
      }
    }, 
    "size": 273789, 
    "width": 519
  }, 

But this is the restapi response when searching for the same item:

https://2023.ploneconf.org/++api++/@search?path.query=accomodation/1111_fotpe1_fachada.jpg

"image_scales": {
        "image": [
          {
            "content-type": "image/jpeg", 
            "download": "@@images/image-519-83d59392a9853e1c02d49572d7f3d9d4.jpeg", 
            "filename": "1111_fotpe1_fachada.jpg", 
            "height": 600, 
            "scales": {
              "icon": {
                "download": "@@images/image-32-4738fb987e65577087fd99b82e06ee18.jpeg", 
                "height": 32, 
                "width": 28
              }, 
              "mini": {
                "download": "@@images/image-200-c46d75543ee91db00d59f7dc25eee6a5.jpeg", 
                "height": 231, 
                "width": 200
              }, 
              "preview": {
                "download": "@@images/image-400-ec03ddc4d1cbbce5b468673412c881b2.jpeg", 
                "height": 462, 
                "width": 400
              }, 
              "thumb": {
                "download": "@@images/image-128-70c2da4ad7ad1d535389d84a456453ee.jpeg", 
                "height": 128, 
                "width": 111
              }, 
              "tile": {
                "download": "@@images/image-64-b7e543169ac9fc5c23180ebba23ddb28.jpeg", 
                "height": 64, 
                "width": 56
              }
            }, 
            "size": 273789, 
            "width": 519
          }
        ]
      }, 

I would find much more consistent to have all of them indexed in the catalog too, otherwise the REST API user (in our case when building a Volto site), needs to do a lot of checks like "if this item hasn't the scale named large use the one named preview and if not mini, etc.)

Missing support to constrain allowed content types

Currently only the image field supports a validation mechanism to check if the uploaded file has the content type "image/*".

The file type should support a similar mime/content type checking mechanism too.

AttributeError: 'ImageScalingQueueDataManager' object has no attribute 'savepoint'

@datakurre

The queue scaling branch throws this exception in a few random cases:

020-05-14 16:41:03,679 ERROR   [Zope.SiteErrorLog:251][waitress] 1589467263.6787560.29447680220729955 http://localhost:20081/plone_portal/russiaplatform/en/news-events/events/POST_application_json_
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 156, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 338, in publish_module
  Module ZPublisher.WSGIPublisher, line 256, in publish
  Module ZPublisher.mapply, line 85, in mapply
  Module ZPublisher.WSGIPublisher, line 62, in call_object
  Module plone.rest.service, line 22, in __call__
  Module plone.restapi.services, line 21, in render
  Module plone.restapi.services.content.add, line 89, in reply
  Module plone.restapi.services.content.utils, line 95, in add
  Module Products.BTreeFolder2.BTreeFolder2, line 464, in _setObject
  Module zope.event, line 32, in notify
  Module zope.component.event, line 27, in dispatch
  Module zope.component._api, line 124, in subscribers
  Module zope.interface.registry, line 442, in subscribers
  Module zope.interface.adapter, line 607, in subscribers
  Module zope.component.event, line 36, in objectEventNotify
  Module zope.component._api, line 124, in subscribers
  Module zope.interface.registry, line 442, in subscribers
  Module zope.interface.adapter, line 607, in subscribers
  Module plone.app.versioningbehavior.subscribers, line 109, in create_initial_version_after_adding
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 336, in save
  Module ugent.patches.products_cmfeditions, line 9, in _recursiveSave
  Module Products.CMFEditions.ArchivistTool, line 267, in prepare
  Module Products.CMFEditions.ModifierRegistryTool, line 135, in getReferencedAttributes
  Module plone.app.versioningbehavior.modifiers, line 116, in getReferencedAttributes
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 410, in retrieve
  Module Products.CMFEditions.CopyModifyMergeRepositoryTool, line 557, in _retrieve
  Module transaction._manager, line 267, in savepoint
  Module transaction._manager, line 147, in savepoint
  Module transaction._transaction, line 229, in savepoint
  Module transaction._transaction, line 345, in _saveAndRaiseCommitishError
  Module transaction._compat, line 50, in reraise
  Module transaction._transaction, line 226, in savepoint
  Module transaction._transaction, line 714, in __init__
TypeError: ('Savepoints unsupported', <plone.namedfile.queue.ImageScalingQueueDataManager object at 0x7f2494d526a0>)
2020-05-14 16:41:04,032 ERROR   [txn.139806391416576:465][waitress] Error in tpc_abort() on manager <Connection at 7f2744a35470>
Traceback (most recent call last):
  File "/home/ajung/sandboxes/ugent-portaal-plone-4x/eggs/transaction-2.4.0-py3.7.egg/transaction/_transaction.py", line 711, in __init__
    savepoint = datamanager.savepoint
AttributeError: 'ImageScalingQueueDataManager' object has no attribute 'savepoint'

Add internal modification date

The goal is to have an internal modification timestamp directly on the namedfile/namedimage.
This makes it possible to use this timestamp for caching/invalidation purposes.

Example:
Currently _p_mtime is used in by the @@images view in Plone to generate a hash_key for scales.
The issue there is, that the metadata in the catalog is not the same, because the catalog gets updated in a precommit hook and thus still has the old _p_mtime.

With an internal modification timestamp this problem can be solved.

Eventually the @@images view can be changed to use this new timestamp, in order to generate resp. invalidate new scales.

IObjectModifiedEvent always shows namedfile has changed

https://community.plone.org/t/namedblobfile-in-event-description-always-modified/10810

If I have a simple namedblobfile in my dexterity type:

class IVideo(model.Schema)
    video_file = NamedBlobFile(
        title=_('video file'),
    )

Then register an event handler when a Video is modified:

  <subscriber
       for="myproduct.video.IVideo
               zope.lifecycleevent.interfaces.IObjectModifiedEvent"
       handler=".events.modifyVideo"
  />

And try to see if the video file was modified:

def modifyVideo(context, event)
    file_changed = False
    if hasattr(event, 'descriptions') and event.descriptions:
        for d in event.descriptions:
            if d.interface is IVideo and 'video_file' in d.attributes:
                file_changed = True

the field 'video_file' will always be present in the list.

When "Keep Existing File" is chosen in the widget, the widget should (??) set the new value to z3c.form.interfaces.NOT_CHANGED according to z3c.form.form 'applyChanges' function.

I'm not sure at all how to fix this myself. I hope I can get some help, if I analyzed the bug properly.

I don't see any documentation for the NOT_CHANGED interface flag - only code:

https://github.com/zopefoundation/z3c.form/blob/e882d357190f6a5bc48dcefe7adc9cfacc9a0ad8/src/z3c/form/form.py#L43

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.