Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: image_scales backport #62

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ jobs:
strategy:
max-parallel: 4
matrix:
python: ["3.7"]
python: ["3.7", "3.8"]
plone: ["52"]
tz: ["UTC", "Europe/Rome"]
# exclude:
# - python: "3.7"
# plone: "51"
Expand All @@ -39,4 +40,4 @@ jobs:
bin/code-analysis
- name: Run tests
run: |
bin/test
TZ=${{ matrix.tz }} bin/test
5 changes: 3 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Changelog
=========

4.1.1 (unreleased)
5.0.0 (unreleased)
------------------

- Nothing changed yet.
- https://github.com/plone/Products.CMFPlone/pull/3521 backport #62
[mamico]


4.1.0 (2022-11-22)
Expand Down
3 changes: 0 additions & 3 deletions base.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ eggs = ${instance:eggs}
[test]
recipe = zc.recipe.testrunner
eggs = ${instance:eggs}
initialization =
os.environ['TZ'] = 'Europe/Rome'
defaults = ['-s', 'redturtle.volto', '--auto-color', '--auto-progress']


Expand All @@ -72,7 +70,6 @@ eggs = coverage
recipe = collective.recipe.template
input = inline:
#!/bin/bash
export TZ=UTC
${buildout:directory}/bin/coverage run bin/test $*
${buildout:directory}/bin/coverage html
${buildout:directory}/bin/coverage report -m --fail-under=90
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"Framework :: Plone :: 5.2",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Operating System :: OS Independent",
"License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
],
Expand Down Expand Up @@ -55,6 +56,7 @@
"kitconcept.seo>=2.0.0",
"plone.volto>3.1.0",
"plone.restapi>=8.16.1",
"plone.namedfile>=6.0.0",
],
extras_require={
"test": [
Expand Down
7 changes: 4 additions & 3 deletions src/redturtle/volto/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# -*- coding: utf-8 -*-
"""Init and utils."""
from zope.i18nmessageid import MessageFactory
from OFS.interfaces import IOrderedContainer
from plone import api
from plone.app.content.browser.vocabulary import PERMISSIONS
from plone.volto import upgrades
from plone.volto.upgrades import MIGRATION
from plone import api
from OFS.interfaces import IOrderedContainer
from zope.i18nmessageid import MessageFactory

import logging


logger = logging.getLogger(__name__)


Expand Down
4 changes: 2 additions & 2 deletions src/redturtle/volto/adapters/stringinterp.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from plone.stringinterp import _ as stringinterp_mf
from plone.stringinterp.adapters import BaseSubstitution
from Products.CMFCore.interfaces import IContentish
from zope.component import adapter
from plone.stringinterp import _ as stringinterp_mf
from redturtle.volto import _
from zope.component import adapter


@adapter(IContentish)
Expand Down
187 changes: 187 additions & 0 deletions src/redturtle/volto/backports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#
# Plone 6.0 backports
#
# [1] https://github.com/plone/Products.CMFPlone/pull/3521
#

from Acquisition import aq_inner
from persistent.dict import PersistentDict
from plone.dexterity.interfaces import IDexterityContent
from plone.dexterity.utils import iterSchemata
from plone.indexer.decorator import indexer
from plone.namedfile.interfaces import INamedImageField
from plone.registry.interfaces import IRegistry
from Products.CMFPlone.interfaces import IImagingSchema
from zope.component import adapter
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.component import queryMultiAdapter
from zope.globalrequest import getRequest
from zope.interface import implementer
from zope.interface import Interface
from zope.schema import getFields


class IImageScalesAdapter(Interface):
"""
Return a list of image scales for the given context
"""

def __init__(context, request):
"""Adapts context and the request."""

def __call__():
""" """


class IImageScalesFieldAdapter(Interface):
""" """

def __init__(field, context, request):
"""Adapts field, context and request."""

def __call__():
"""Returns JSON compatible python data."""


@indexer(IDexterityContent)
def image_scales(obj):
"""
Indexer used to store in metadata the image scales of the object.
"""
adapter = queryMultiAdapter((obj, getRequest()), IImageScalesAdapter)
if not adapter:
# Raising an AttributeError does the right thing,
# making sure nothing is saved in the catalog.
raise AttributeError
scales = adapter()
if not scales:
raise AttributeError
return PersistentDict(scales)


@implementer(IImageScalesAdapter)
@adapter(IDexterityContent, Interface)
class ImageScales:
def __init__(self, context, request):
self.context = context
self.request = request

def __call__(self):
obj = aq_inner(self.context)
res = {}
for schema in iterSchemata(self.context):
for name, field in getFields(schema).items():
# serialize the field
serializer = queryMultiAdapter(
(field, obj, self.request), IImageScalesFieldAdapter
)
if serializer:
scales = serializer()
if scales:
res[name] = scales
return res


def _split_scale_info(allowed_size):
name, dims = allowed_size.split(" ")
width, height = list(map(int, dims.split(":")))
return name, width, height


def _get_scale_infos():
"""Returns list of (name, width, height) of the available image scales."""
registry = getUtility(IRegistry)
imaging_settings = registry.forInterface(IImagingSchema, prefix="plone")
allowed_sizes = imaging_settings.allowed_sizes
return [_split_scale_info(size) for size in allowed_sizes]


@implementer(IImageScalesFieldAdapter)
@adapter(INamedImageField, IDexterityContent, Interface)
class ImageFieldScales:
def __init__(self, field, context, request):
self.context = context
self.request = request
self.field = field

def __call__(self):
image = self.field.get(self.context)
if not image:
return

# Get the @@images view once and store it, so all methods can use it.
self.images_view = getMultiAdapter((self.context, self.request), name="images")
width, height = image.getImageSize()
url = self.get_original_image_url(self.field.__name__, width, height)
scales = self.get_scales(self.field, width, height)

# Return a list with one dictionary. Why a list?
# Some people feel a need in custom code to support a different adapter for
# RelationList fields. Such a field may point to three images.
# In that case the adapter could return information for all three images,
# so a list of three dictionaries. The default case should use the same
# structure.
return [
{
"filename": image.filename,
"content-type": image.contentType,
"size": image.getSize(),
"download": url,
"width": width,
"height": height,
"scales": scales,
}
]

def get_scales(self, field, width, height):
"""Get a dictionary of available scales for a particular image field,
with the actual dimensions (aspect ratio of the original image).
"""
scales = {}

for name, actual_width, actual_height in _get_scale_infos():
if actual_width > width:
# The width of the scale is larger than the original width.
# Scaling would simply return the original (or perhaps a copy
# with the same size). We do not need this scale.
# If we *do* want this, we should call the scale method with
# mode="cover", so it scales up.
continue

# Get the scale info without actually generating the scale,
# nor any old-style HiDPI scales.
scale = self.images_view.scale(
field.__name__,
width=actual_width,
height=actual_height,
pre=True,
include_srcset=False,
)
if scale is None:
# If we cannot get a scale, it is probably a corrupt image.
continue

url = scale.url
actual_width = scale.width
actual_height = scale.height

scales[name] = {
"download": url,
"width": actual_width,
"height": actual_height,
}

return scales

def get_original_image_url(self, fieldname, width, height):
scale = self.images_view.scale(
fieldname,
width=width,
height=height,
direction="thumbnail",
pre=True,
include_srcset=False,
)
# Corrupt images may not have a scale.
return scale.url if scale else None
7 changes: 4 additions & 3 deletions src/redturtle/volto/browser/find_blocks.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
from Acquisition import aq_base
from copy import deepcopy
from Products.Five import BrowserView
from plone import api
from Acquisition import aq_base
from plone.dexterity.utils import iterSchemata
from Products.Five import BrowserView
from zope.schema import getFieldsInOrder

import logging
import json
import logging


logger = logging.getLogger(__name__)

Expand Down
9 changes: 5 additions & 4 deletions src/redturtle/volto/browser/fix_links.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
from Products.Five import BrowserView
from plone import api
from Acquisition import aq_base
from plone import api
from plone.dexterity.utils import iterSchemata
from zope.schema import getFieldsInOrder
from plone.restapi.interfaces import IFieldDeserializer
from Products.Five import BrowserView
from zope.component import queryMultiAdapter
from zope.schema import getFieldsInOrder

import json
import logging
import re
import json


logger = logging.getLogger(__name__)

Expand Down
14 changes: 13 additions & 1 deletion src/redturtle/volto/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
xmlns="http://namespaces.zope.org/zope"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:zcml="http://namespaces.zope.org/zcml"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="redturtle.volto">

Expand All @@ -12,12 +13,23 @@
<include package=".browser" />
<include package=".restapi" />
<include package=".types" />

<include file="indexers.zcml" />
<include file="monkey.zcml" />
<include file="permissions.zcml" />
<include file="upgrades.zcml" />

<!-- https://github.com/plone/Products.CMFPlone/pull/3521/ backport -->
<!-- TODO: upgrade step -->
<configure zcml:condition="not-have plone-60">
<adapter factory=".backports.image_scales" name="image_scales" />
<adapter factory=".backports.ImageScales" />
<!-- TODO:
verificare se con plone.namedfile 6.0.0 questo adapter
potrebbe diventare non necessario -->
<adapter factory=".backports.ImageFieldScales" />
</configure>

<genericsetup:registerProfile
name="default"
title="RedTurtle: Volto"
Expand Down
2 changes: 1 addition & 1 deletion src/redturtle/volto/monkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from lxml import etree
from lxml import html
from lxml.html.clean import Cleaner
from plone.app.caching import purge
from plone.app.event.base import dt_start_of_day
from plone.app.event.recurrence import Occurrence
from plone.app.caching import purge
from plone.event.interfaces import IEventAccessor
from plone.event.recurrence import recurrence_sequence_ical
from plone.event.utils import pydt
Expand Down
2 changes: 2 additions & 0 deletions src/redturtle/volto/profiles/default/catalog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
<column value="open_end"/>
<column value="whole_day"/>
<column value="recurrence"/>
<!-- backport -->
<column value="image_scales"/>
</object>
10 changes: 4 additions & 6 deletions src/redturtle/volto/restapi/deserializer/dxfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
from plone.app.textfield.interfaces import IRichText
from plone.dexterity.interfaces import IDexterityContent
from plone.restapi.deserializer.blocks import path2uid
from plone.restapi.deserializer.dxfields import (
DatetimeFieldDeserializer as DefaultDatetimeFieldDeserializer,
RichTextFieldDeserializer as BaseRichTextDeserializer,
TextLineFieldDeserializer as BaseTextLineDeserializer,
)
from plone.restapi.deserializer.dxfields import DatetimeFieldDeserializer as DefaultDatetimeFieldDeserializer
from plone.restapi.deserializer.dxfields import RichTextFieldDeserializer as BaseRichTextDeserializer
from plone.restapi.deserializer.dxfields import TextLineFieldDeserializer as BaseTextLineDeserializer
from plone.restapi.interfaces import IFieldDeserializer
from Products.CMFPlone.utils import safe_unicode
from pytz import timezone
Expand Down Expand Up @@ -77,7 +75,7 @@ def __call__(self, value):
@adapter(IDatetime, IDexterityContent, IRedturtleVoltoLayer)
class DatetimeFieldDeserializer(DefaultDatetimeFieldDeserializer):
def __call__(self, value):
"""
"""TODO: explain this and create some unit tests
"""
# PATCH
is_publication_field = self.field.interface == IPublication
Expand Down
Loading