--- a/src/pyams_file/file.py Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/file.py Wed Jun 27 12:19:42 2018 +0200
@@ -254,6 +254,15 @@
request = check_request()
request.registry.notify(FileModifiedEvent(self))
+ def rotate(self, angle=-90):
+ image = Image.open(self.get_blob(mode='c'))
+ new_image = BytesIO()
+ image.rotate(angle, expand=True) \
+ .save(new_image, image.format, quality=99)
+ self.data = new_image
+ request = check_request()
+ request.registry.notify(FileModifiedEvent(self))
+
@implementer(ISVGImage)
class SVGImageFile(File):
--- a/src/pyams_file/interfaces/__init__.py Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/interfaces/__init__.py Wed Jun 27 12:19:42 2018 +0200
@@ -84,6 +84,9 @@
def crop(self, x1, y1, x2, y2):
"""Crop image to given coordinates"""
+ def rotate(self, angle=-90):
+ """Rotate image, default to right"""
+
class ISVGImage(IBaseImage):
"""SVG file interface"""
Binary file src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.mo has changed
--- a/src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/locales/fr/LC_MESSAGES/pyams_file.po Wed Jun 27 12:19:42 2018 +0200
@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-06-08 12:31+0200\n"
+"POT-Creation-Date: 2018-06-27 12:17+0200\n"
"PO-Revision-Date: 2015-02-06 21:39+0100\n"
"Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
"Language-Team: French\n"
@@ -71,13 +71,9 @@
msgid "Delete content"
msgstr "Supprimer ce contenu"
-#: src/pyams_file/widget/templates/media-input.pt:30
-#: src/pyams_file/widget/templates/media-display.pt:12
-msgid "Zoom image"
-msgstr "Agrandir l'image"
-
-#: src/pyams_file/widget/templates/media-input.pt:36
-#: src/pyams_file/widget/templates/media-input.pt:68
+#: src/pyams_file/widget/templates/media-input.pt:27
+#: src/pyams_file/widget/templates/media-input.pt:64
+#: src/pyams_file/widget/templates/media-input.pt:96
#: src/pyams_file/widget/templates/file-input.pt:31
#: src/pyams_file/widget/templates/file-display.pt:14
#: src/pyams_file/widget/templates/media-display.pt:20
@@ -85,8 +81,9 @@
msgid "Current value:"
msgstr "Contenu actuel :"
-#: src/pyams_file/widget/templates/media-input.pt:52
-#: src/pyams_file/widget/templates/media-input.pt:82
+#: src/pyams_file/widget/templates/media-input.pt:41
+#: src/pyams_file/widget/templates/media-input.pt:80
+#: src/pyams_file/widget/templates/media-input.pt:110
#: src/pyams_file/widget/templates/file-input.pt:45
#: src/pyams_file/widget/templates/file-display.pt:21
#: src/pyams_file/widget/templates/media-display.pt:30
@@ -94,6 +91,11 @@
msgid "Download"
msgstr "Enregistrer sous..."
+#: src/pyams_file/widget/templates/media-input.pt:55
+#: src/pyams_file/widget/templates/media-display.pt:12
+msgid "Zoom image"
+msgstr "Agrandir l'image"
+
#: src/pyams_file/zmi/file.py:42
msgid "Properties..."
msgstr "Propriétés"
@@ -102,15 +104,19 @@
msgid "Update file properties"
msgstr "Mise à jour des propriétés"
-#: src/pyams_file/zmi/image.py:61
+#: src/pyams_file/zmi/image.py:107
+msgid "Rotate image to right..."
+msgstr "Tourner l'image vers la droite"
+
+#: src/pyams_file/zmi/image.py:156
msgid "Crop image..."
msgstr "Recadrer l'image"
-#: src/pyams_file/zmi/image.py:83
+#: src/pyams_file/zmi/image.py:178
msgid "Crop image"
msgstr "Recadrer l'image"
-#: src/pyams_file/zmi/image.py:126
+#: src/pyams_file/zmi/image.py:226
msgid ""
"You can use this form to crop an image.\n"
"\n"
@@ -128,7 +134,7 @@
"ou les vignettes déjà sélectionnées sur la base de l'ancienne image sont "
"réinitialisées !"
-#: src/pyams_file/zmi/image.py:140
+#: src/pyams_file/zmi/image.py:240
msgid "You can use this form to make a selection on an image."
msgstr ""
"Par défaut, l'image est affichée dans son intégralité quel que soit le type "
@@ -141,39 +147,39 @@
"**ATTENTION** : si l'image d'origine est recadrée ou rechargée, la fonction "
"est réinitialisée et il faut procéder à un nouveau choix."
-#: src/pyams_file/zmi/image.py:160
+#: src/pyams_file/zmi/image.py:308
msgid "Select portrait thumbnail..."
msgstr "Vignette portrait"
-#: src/pyams_file/zmi/image.py:182
+#: src/pyams_file/zmi/image.py:330
msgid "Select portrait thumbnail"
msgstr "Emprise de la vignette en mode portrait"
-#: src/pyams_file/zmi/image.py:225
+#: src/pyams_file/zmi/image.py:341
msgid ""
"You can use this form to select a portrait thumbnail of this image.\n"
"\n"
"**WARNING**: cropping or resizing an image will reset all selected "
"thumbnails and adaptive images!!"
msgstr ""
-"L'utilisation d'une vignette en mode portrait n'est pas systématique, elle dépend "
-"du modèle de présentation qui peut ou non y faire appel. Par défaut, la "
-"vignette est positionnée au centre de l'image, la sélection de "
-"son emprise ne modifie pas l'image d'origine.\n"
+"L'utilisation d'une vignette en mode portrait n'est pas systématique, elle "
+"dépend du modèle de présentation qui peut ou non y faire appel. Par défaut, "
+"la vignette est positionnée au centre de l'image, la sélection de son "
+"emprise ne modifie pas l'image d'origine.\n"
"\n"
"**ATTENTION** : lorsqu'une image est recadrée, redimentionnée ou rechargée, "
"la sélection est réinitialisée sur sa position par défaut (centrée) ; s'il y "
"a lieu, vous devez procéder à une nouvelle sélection personnalisée."
-#: src/pyams_file/zmi/image.py:240
+#: src/pyams_file/zmi/image.py:356
msgid "Select square thumbnail..."
msgstr "Vignette carrée"
-#: src/pyams_file/zmi/image.py:262
+#: src/pyams_file/zmi/image.py:378
msgid "Select square thumbnail"
msgstr "Emprise de la vignette carrée"
-#: src/pyams_file/zmi/image.py:305
+#: src/pyams_file/zmi/image.py:389
msgid ""
"You can use this form to select a square thumbnail of this image.\n"
"\n"
@@ -189,15 +195,15 @@
"la sélection est réinitialisée sur sa position par défaut (centrée) ; s'il y "
"a lieu, vous devez procéder à une nouvelle sélection personnalisée."
-#: src/pyams_file/zmi/image.py:320
+#: src/pyams_file/zmi/image.py:404
msgid "Select panoramic thumbnail..."
msgstr "Vignette panoramique"
-#: src/pyams_file/zmi/image.py:342
+#: src/pyams_file/zmi/image.py:426
msgid "Select panoramic thumbnail"
msgstr "Emprise de la vignette panoramique"
-#: src/pyams_file/zmi/image.py:385
+#: src/pyams_file/zmi/image.py:437
msgid ""
"You can use this form to select a panoramic thumbnail of this image.\n"
"\n"
@@ -213,55 +219,55 @@
"la sélection est réinitialisée sur sa position par défaut (centrée) ; s'il y "
"a lieu, vous devez procéder à une nouvelle sélection personnalisée."
-#: src/pyams_file/zmi/image.py:458
+#: src/pyams_file/zmi/image.py:452
msgid "Select responsive XS image..."
msgstr "Image adaptative pour smartphones"
-#: src/pyams_file/zmi/image.py:479
+#: src/pyams_file/zmi/image.py:473
msgid "Select image for extra-small (XS) devices"
msgstr "Portion de l'image affichée sur les smartphones (taille XS)"
-#: src/pyams_file/zmi/image.py:494
+#: src/pyams_file/zmi/image.py:488
msgid "Select responsive SM image..."
msgstr "Image adaptative pour tablettes"
-#: src/pyams_file/zmi/image.py:515
+#: src/pyams_file/zmi/image.py:509
msgid "Select image for small (SM) devices"
msgstr "Portion de l'image affichée sur les tablettes (taille SM)"
-#: src/pyams_file/zmi/image.py:530
+#: src/pyams_file/zmi/image.py:524
msgid "Select responsive MD image..."
msgstr "Image adaptative pour terminaux moyens"
-#: src/pyams_file/zmi/image.py:551
+#: src/pyams_file/zmi/image.py:545
msgid "Select image for medium (MD) devices"
msgstr "Portion de l'image affichée sur les terminaux moyens (taille MD)"
-#: src/pyams_file/zmi/image.py:566
+#: src/pyams_file/zmi/image.py:560
msgid "Select responsive LG image..."
msgstr "Image adaptative pour grands terminaux"
-#: src/pyams_file/zmi/image.py:587
+#: src/pyams_file/zmi/image.py:581
msgid "Select image for large (LG) devices"
msgstr "Portion de l'image affichée sur les grands terminaux (taille LG)"
-#: src/pyams_file/zmi/image.py:602
+#: src/pyams_file/zmi/image.py:596
msgid "Display all thumbnails"
msgstr "Voir toutes les vignettes"
-#: src/pyams_file/zmi/image.py:619
+#: src/pyams_file/zmi/image.py:613
msgid "Display all image thumbnails"
msgstr "Récapitulatif des vignettes associées"
-#: src/pyams_file/zmi/image.py:664
+#: src/pyams_file/zmi/image.py:654
msgid "Resize image..."
msgstr "Redimensionner l'image"
-#: src/pyams_file/zmi/image.py:718 src/pyams_file/zmi/image.py:675
+#: src/pyams_file/zmi/image.py:708 src/pyams_file/zmi/image.py:665
msgid "Resize image"
msgstr "Redimensionner l'image"
-#: src/pyams_file/zmi/image.py:749
+#: src/pyams_file/zmi/image.py:739
msgid ""
"You can use this form to change image dimensions.\n"
"\n"
@@ -273,32 +279,32 @@
"Une nouvelle image ne sera générée que si les dimensions indiquées sont "
"inférieures à la taille du fichier actuel."
-#: src/pyams_file/zmi/image.py:71 src/pyams_file/zmi/image.py:147
-#: src/pyams_file/zmi/image.py:398 src/pyams_file/zmi/image.py:674
+#: src/pyams_file/zmi/image.py:166 src/pyams_file/zmi/image.py:247
+#: src/pyams_file/zmi/image.py:664
msgid "Close"
msgstr "Fermer"
-#: src/pyams_file/zmi/image.py:72
+#: src/pyams_file/zmi/image.py:167
msgid "Crop"
msgstr "Recadrer l'image"
-#: src/pyams_file/zmi/image.py:148 src/pyams_file/zmi/image.py:399
+#: src/pyams_file/zmi/image.py:248
msgid "Select thumbnail"
msgstr "Sélectionner cette portion d'image"
-#: src/pyams_file/zmi/image.py:681
+#: src/pyams_file/zmi/image.py:671
msgid "New image width"
msgstr "Largeur de l'image"
-#: src/pyams_file/zmi/image.py:683
+#: src/pyams_file/zmi/image.py:673
msgid "New image height"
msgstr "Hauteur de l'image"
-#: src/pyams_file/zmi/image.py:685
+#: src/pyams_file/zmi/image.py:675
msgid "Keep aspect ratio"
msgstr "Ne pas déformer l'image"
-#: src/pyams_file/zmi/image.py:686
+#: src/pyams_file/zmi/image.py:676
msgid ""
"Check to keep original aspect ratio; image will be resized as large as "
"possible within given limits"
@@ -307,27 +313,27 @@
"L'image sera redimensionnée (sans jamais être agrandie !) pour être aussi "
"grande que possible en fonction des contraintes indiquées."
-#: src/pyams_file/interfaces/__init__.py:99
+#: src/pyams_file/interfaces/__init__.py:110
msgid "Title"
msgstr "Titre"
-#: src/pyams_file/interfaces/__init__.py:102
+#: src/pyams_file/interfaces/__init__.py:113
msgid "Description"
msgstr "Description"
-#: src/pyams_file/interfaces/__init__.py:105
+#: src/pyams_file/interfaces/__init__.py:116
msgid "Save file as..."
msgstr "Enregistrer sous..."
-#: src/pyams_file/interfaces/__init__.py:106
+#: src/pyams_file/interfaces/__init__.py:117
msgid "Name under which the file will be saved"
msgstr "Nom proposé automatiquement lors de l'enregistrement du fichier"
-#: src/pyams_file/interfaces/__init__.py:109
+#: src/pyams_file/interfaces/__init__.py:120
msgid "Language"
msgstr "Langue"
-#: src/pyams_file/interfaces/__init__.py:110
+#: src/pyams_file/interfaces/__init__.py:121
msgid "File's content language"
msgstr "Langue du contenu du fichier"
--- a/src/pyams_file/locales/pyams_file.pot Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/locales/pyams_file.pot Wed Jun 27 12:19:42 2018 +0200
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-06-08 12:31+0200\n"
+"POT-Creation-Date: 2018-06-27 12:17+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -71,13 +71,9 @@
msgid "Delete content"
msgstr ""
-#: ./src/pyams_file/widget/templates/media-input.pt:30
-#: ./src/pyams_file/widget/templates/media-display.pt:12
-msgid "Zoom image"
-msgstr ""
-
-#: ./src/pyams_file/widget/templates/media-input.pt:36
-#: ./src/pyams_file/widget/templates/media-input.pt:68
+#: ./src/pyams_file/widget/templates/media-input.pt:27
+#: ./src/pyams_file/widget/templates/media-input.pt:64
+#: ./src/pyams_file/widget/templates/media-input.pt:96
#: ./src/pyams_file/widget/templates/file-input.pt:31
#: ./src/pyams_file/widget/templates/file-display.pt:14
#: ./src/pyams_file/widget/templates/media-display.pt:20
@@ -85,8 +81,9 @@
msgid "Current value:"
msgstr ""
-#: ./src/pyams_file/widget/templates/media-input.pt:52
-#: ./src/pyams_file/widget/templates/media-input.pt:82
+#: ./src/pyams_file/widget/templates/media-input.pt:41
+#: ./src/pyams_file/widget/templates/media-input.pt:80
+#: ./src/pyams_file/widget/templates/media-input.pt:110
#: ./src/pyams_file/widget/templates/file-input.pt:45
#: ./src/pyams_file/widget/templates/file-display.pt:21
#: ./src/pyams_file/widget/templates/media-display.pt:30
@@ -94,6 +91,11 @@
msgid "Download"
msgstr ""
+#: ./src/pyams_file/widget/templates/media-input.pt:55
+#: ./src/pyams_file/widget/templates/media-display.pt:12
+msgid "Zoom image"
+msgstr ""
+
#: ./src/pyams_file/zmi/file.py:42
msgid "Properties..."
msgstr ""
@@ -102,176 +104,180 @@
msgid "Update file properties"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:61
+#: ./src/pyams_file/zmi/image.py:107
+msgid "Rotate image to right..."
+msgstr ""
+
+#: ./src/pyams_file/zmi/image.py:156
msgid "Crop image..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:83
+#: ./src/pyams_file/zmi/image.py:178
msgid "Crop image"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:126
+#: ./src/pyams_file/zmi/image.py:226
msgid ""
"You can use this form to crop an image.\n"
"\n"
"**WARNING**: cropping an image will reset all selected thumbnails and adaptive images!!"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:140
+#: ./src/pyams_file/zmi/image.py:240
msgid "You can use this form to make a selection on an image."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:160
+#: ./src/pyams_file/zmi/image.py:308
msgid "Select portrait thumbnail..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:182
+#: ./src/pyams_file/zmi/image.py:330
msgid "Select portrait thumbnail"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:225
+#: ./src/pyams_file/zmi/image.py:341
msgid ""
"You can use this form to select a portrait thumbnail of this image.\n"
"\n"
"**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:240
+#: ./src/pyams_file/zmi/image.py:356
msgid "Select square thumbnail..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:262
+#: ./src/pyams_file/zmi/image.py:378
msgid "Select square thumbnail"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:305
+#: ./src/pyams_file/zmi/image.py:389
msgid ""
"You can use this form to select a square thumbnail of this image.\n"
"\n"
"**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:320
+#: ./src/pyams_file/zmi/image.py:404
msgid "Select panoramic thumbnail..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:342
+#: ./src/pyams_file/zmi/image.py:426
msgid "Select panoramic thumbnail"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:385
+#: ./src/pyams_file/zmi/image.py:437
msgid ""
"You can use this form to select a panoramic thumbnail of this image.\n"
"\n"
"**WARNING**: cropping or resizing an image will reset all selected thumbnails and adaptive images!!"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:458
+#: ./src/pyams_file/zmi/image.py:452
msgid "Select responsive XS image..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:479
+#: ./src/pyams_file/zmi/image.py:473
msgid "Select image for extra-small (XS) devices"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:494
+#: ./src/pyams_file/zmi/image.py:488
msgid "Select responsive SM image..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:515
+#: ./src/pyams_file/zmi/image.py:509
msgid "Select image for small (SM) devices"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:530
+#: ./src/pyams_file/zmi/image.py:524
msgid "Select responsive MD image..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:551
+#: ./src/pyams_file/zmi/image.py:545
msgid "Select image for medium (MD) devices"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:566
+#: ./src/pyams_file/zmi/image.py:560
msgid "Select responsive LG image..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:587
+#: ./src/pyams_file/zmi/image.py:581
msgid "Select image for large (LG) devices"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:602
+#: ./src/pyams_file/zmi/image.py:596
msgid "Display all thumbnails"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:619
+#: ./src/pyams_file/zmi/image.py:613
msgid "Display all image thumbnails"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:664
+#: ./src/pyams_file/zmi/image.py:654
msgid "Resize image..."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:718 ./src/pyams_file/zmi/image.py:675
+#: ./src/pyams_file/zmi/image.py:708 ./src/pyams_file/zmi/image.py:665
msgid "Resize image"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:749
+#: ./src/pyams_file/zmi/image.py:739
msgid ""
"You can use this form to change image dimensions.\n"
"\n"
"This will generate a new image only if requested size is smaller than the original one."
msgstr ""
-#: ./src/pyams_file/zmi/image.py:71 ./src/pyams_file/zmi/image.py:147
-#: ./src/pyams_file/zmi/image.py:398 ./src/pyams_file/zmi/image.py:674
+#: ./src/pyams_file/zmi/image.py:166 ./src/pyams_file/zmi/image.py:247
+#: ./src/pyams_file/zmi/image.py:664
msgid "Close"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:72
+#: ./src/pyams_file/zmi/image.py:167
msgid "Crop"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:148 ./src/pyams_file/zmi/image.py:399
+#: ./src/pyams_file/zmi/image.py:248
msgid "Select thumbnail"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:681
+#: ./src/pyams_file/zmi/image.py:671
msgid "New image width"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:683
+#: ./src/pyams_file/zmi/image.py:673
msgid "New image height"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:685
+#: ./src/pyams_file/zmi/image.py:675
msgid "Keep aspect ratio"
msgstr ""
-#: ./src/pyams_file/zmi/image.py:686
+#: ./src/pyams_file/zmi/image.py:676
msgid ""
"Check to keep original aspect ratio; image will be resized as large as "
"possible within given limits"
msgstr ""
-#: ./src/pyams_file/interfaces/__init__.py:99
+#: ./src/pyams_file/interfaces/__init__.py:110
msgid "Title"
msgstr ""
-#: ./src/pyams_file/interfaces/__init__.py:102
+#: ./src/pyams_file/interfaces/__init__.py:113
msgid "Description"
msgstr ""
-#: ./src/pyams_file/interfaces/__init__.py:105
+#: ./src/pyams_file/interfaces/__init__.py:116
msgid "Save file as..."
msgstr ""
-#: ./src/pyams_file/interfaces/__init__.py:106
+#: ./src/pyams_file/interfaces/__init__.py:117
msgid "Name under which the file will be saved"
msgstr ""
-#: ./src/pyams_file/interfaces/__init__.py:109
+#: ./src/pyams_file/interfaces/__init__.py:120
msgid "Language"
msgstr ""
-#: ./src/pyams_file/interfaces/__init__.py:110
+#: ./src/pyams_file/interfaces/__init__.py:121
msgid "File's content language"
msgstr ""
--- a/src/pyams_file/widget/__init__.py Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/widget/__init__.py Wed Jun 27 12:19:42 2018 +0200
@@ -24,6 +24,7 @@
from pyams_form.interfaces.form import IFormLayer
from pyramid.interfaces import IView
from z3c.form.interfaces import NOT_CHANGED, IFieldWidget, IDataConverter
+from zope.dublincore.interfaces import IZopeDublinCore
# import packages
from pyams_file.file import EXTENSIONS_THUMBNAILS
@@ -72,7 +73,11 @@
@property
def timestamp(self):
- return datetime.utcnow().timestamp()
+ dc = IZopeDublinCore(self.current_value, None)
+ if dc is None:
+ return datetime.utcnow().timestamp()
+ else:
+ return dc.modified.timestamp()
@property
def current_value(self):
--- a/src/pyams_file/widget/templates/media-display.pt Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/widget/templates/media-display.pt Wed Jun 27 12:19:42 2018 +0200
@@ -10,10 +10,10 @@
target python:thumbnails.get_thumbnail('800x600', 'jpeg');"
tal:attributes="href extension:absolute_url(target);"
title="Zoom image" i18n:attributes="title">
- <img class="thumbnail"
+ <img class="thumbnail" title="" src="" alt=""
tal:define="thumbnail python:thumbnails.get_thumbnail('128x128');"
- tal:attributes="src extension:absolute_url(thumbnail);
- title i18n:value.title;" title="" src="" alt="" />
+ tal:attributes="src string:${extension:absolute_url(thumbnail)}?_=${extension:timestamp(thumbnail)};
+ title i18n:value.title;" />
</a>
<tal:if condition="python:value.content_type.startswith('image/')">
<div class="margin-top-5">
--- a/src/pyams_file/widget/templates/media-input.pt Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/widget/templates/media-input.pt Wed Jun 27 12:19:42 2018 +0200
@@ -53,9 +53,12 @@
target python:view.get_thumbnail('800x600');"
tal:attributes="href extension:absolute_url(target);"
title="Zoom image" i18n:attributes="title">
- <img class="thumbnail"
- tal:attributes="src extension:absolute_url(thumbnail);
- title i18n:value.title;" title="" src="" alt="" />
+ <img class="thumbnail" title="" src="" alt=""
+ tal:define="url extension:absolute_url(thumbnail);
+ timestamp extension:timestamp(thumbnail);"
+ tal:attributes="id string:thumbnail_${extension:cache_key(value)};
+ src string:${url}?_=${timestamp};
+ title i18n:value.title;" />
</a>
<div class="margin-top-5">
<span i18n:translate="">Current value: </span>
--- a/src/pyams_file/zmi/image.py Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/zmi/image.py Wed Jun 27 12:19:42 2018 +0200
@@ -9,6 +9,7 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
+from transaction.interfaces import ITransactionManager
__docformat__ = 'restructuredtext'
@@ -24,10 +25,12 @@
from pyams_file.interfaces import IImage, IThumbnail, IResponsiveImage, IFileModifierForm, IThumbnailer, \
IFileInfo, IThumbnailForm, IMediaWidget, ISVGImage
from pyams_form.interfaces.form import IWidgetsPrefixViewletsManager, IFormHelp
+from pyams_i18n.interfaces import II18n
from pyams_skin.interfaces.viewlet import IContextActions
from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import VIEW_PERMISSION
+from pyams_utils.interfaces import VIEW_PERMISSION, ICacheKeyValue
from pyams_zmi.layer import IAdminLayer
+from zope.dublincore.interfaces import IZopeDublinCore
# import packages
from pyams_file.image import ThumbnailGeometry
@@ -43,6 +46,7 @@
from pyams_viewlet.viewlet import viewlet_config, Viewlet
from pyams_zmi.form import AdminDialogEditForm
from pyramid.renderers import render
+from pyramid.view import view_config
from z3c.form import field, button
from zope.interface import implementer, Interface
from zope.schema import Int, Bool
@@ -92,6 +96,55 @@
#
+# Image rotate
+#
+
+@viewlet_config(name='image-rotate.action', context=IImage, layer=IPyAMSLayer, view=IMediaWidget,
+ manager=IContextActions, weight=20)
+class ImageRotateAction(FileModifierAction):
+ """Image rotate action"""
+
+ label = _("Rotate image to right...")
+ label_css_class = 'fa fa-fw-md fa-rotate-right'
+
+ @property
+ def url(self):
+ return 'MyAMS.ajax.getJSON?url={0}'.format(absolute_url(self.context, self.request, 'rotate.json'))
+
+ def get_url(self):
+ return self.url
+
+
+@view_config(name='rotate.json', context=IImage, request_type=IPyAMSLayer,
+ permission=VIEW_PERMISSION, renderer='json', xhr=True)
+def rotate_image(request):
+ """Rotate given image to right"""
+ image = request.context
+ IImage(image).rotate(-90)
+ # Commit to save blobs!!
+ ITransactionManager(image).commit()
+ cache_key = ICacheKeyValue(image)
+ thumbnail = IThumbnail(image).get_thumbnail('128x128')
+ dc = IZopeDublinCore(thumbnail)
+ return {
+ 'status': 'success',
+ 'message': request.localizer.translate(AdminDialogEditForm.successMessage),
+ 'callbacks': [{
+ 'callback': 'MyAMS.skin.refreshContent',
+ 'options': {
+ 'object_id': 'thumbnail_{0}'.format(cache_key),
+ 'content': '<img class="thumbnail" id="thumbnail_{key}" src="{src}" title="{title}" />'.format(
+ key=cache_key,
+ src='{0}?_={1}'.format(absolute_url(thumbnail, request), dc.modified.timestamp()),
+ title=II18n(image).query_attribute('title', request=request)
+ )
+ }
+ }],
+ 'close_form': False
+ }
+
+
+#
# Image crop
#
@@ -134,6 +187,11 @@
def title(self):
return self.context.title or self.context.filename
+ @property
+ def timestamp(self):
+ dc = IZopeDublinCore(self.context)
+ return dc.modified.timestamp()
+
def updateActions(self):
super(ImageCropForm, self).updateActions()
if 'crop' in self.actions:
@@ -232,6 +290,11 @@
class ImageSelectionThumbnailViewletsPrefix(Viewlet):
"""Image square thumbnail viewlets prefix"""
+ @property
+ def timestamp(self):
+ dc = IZopeDublinCore(self.context)
+ return dc.modified.timestamp()
+
#
# Image portrait thumbnail selection
@@ -560,10 +623,6 @@
class ImageThumbnailsViewletsPrefix(Viewlet):
"""Image thumbnails viewlets prefix"""
- @property
- def random(self):
- return random.randint(0, sys.maxsize)
-
def get_thumbnails(self):
registry = self.request.registry
translate = self.request.localizer.translate
--- a/src/pyams_file/zmi/templates/image-crop.pt Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/zmi/templates/image-crop.pt Wed Jun 27 12:19:42 2018 +0200
@@ -9,7 +9,9 @@
<img class="imgareaselect"
data-ams-imgareaselect-parent=".modal-dialog"
data-ams-imgareaselect-target-field="selection."
- tal:attributes="src extension:absolute_url(image);
+ tal:define="url extension:absolute_url(image);
+ timestamp extension:timestamp(image);"
+ tal:attributes="src string:${url}?_=${timestamp};
width thumb_size[0];
height thumb_size[1];
data-ams-imgareaselect-image-width size[0];
--- a/src/pyams_file/zmi/templates/image-selection.pt Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/zmi/templates/image-selection.pt Wed Jun 27 12:19:42 2018 +0200
@@ -10,7 +10,9 @@
<img class="imgareaselect"
data-ams-imgareaselect-parent=".modal-dialog"
data-ams-imgareaselect-target-field="selection."
- tal:attributes="src extension:absolute_url(image);
+ tal:define="url extension:absolute_url(image);
+ timestamp extension:timestamp(image);"
+ tal:attributes="src string:${url}?_=${timestamp};
width thumb_size[0];
height thumb_size[1];
data-ams-imgareaselect-ratio view.__parent__.selection_ratio;
--- a/src/pyams_file/zmi/templates/image-thumbnails.pt Sat Jun 23 13:02:47 2018 +0200
+++ b/src/pyams_file/zmi/templates/image-thumbnails.pt Wed Jun 27 12:19:42 2018 +0200
@@ -11,7 +11,7 @@
<img tal:define="base extension:absolute_url(context);
name adapter['name'];
thname '{0}:'.format(name) if name else '';"
- tal:attributes="src string:${base}/++thumb++${thname}200x128.jpeg?_=${view.random}" />
+ tal:attributes="src string:${base}/++thumb++${thname}200x128.jpeg?_=${extension:timestamp(context)}" />
</div>
</div>
</tal:loop>