diff --git a/.gitignore b/.gitignore index 17bf7be..4b49871 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,3 @@ output.xml include/ lib/ pip-selfcheck.json -buildout.cfg diff --git a/.travis.yml b/.travis.yml index 4f60eda..2b333bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,17 +4,16 @@ sudo: false cache: pip: true directories: - - $HOME/buildout-cache + - eggs env: # - PLONE_VERSION=4.3.x - PLONE_VERSION=5.0.x -before_install: - - mkdir -p $HOME/buildout-cache/{eggs,downloads} - - virtualenv . - - bin/pip install --upgrade pip setuptools zc.buildout + - PLONE_VERSION=5.1.x install: - - sed -ie "s#plone-x.x.x.cfg#plone-$PLONE_VERSION.cfg#" travis.cfg - - bin/buildout -N -t 3 -c travis.cfg + - virtualenv -p `which python` . + - bin/pip install -r requirements.txt + - sed -ie "s#test-5.1.x#test-$PLONE_VERSION#" buildout.cfg + - bin/buildout -N -t 5 script: - bin/code-analysis - bin/test diff --git a/base.cfg b/buildout.cfg similarity index 69% rename from base.cfg rename to buildout.cfg index d65f9d0..1f70cdd 100644 --- a/base.cfg +++ b/buildout.cfg @@ -1,34 +1,19 @@ [buildout] +extends= + https://raw.github.com/collective/buildout.plonetest/master/test-5.1.x.cfg + versions.cfg + extensions = mr.developer -parts = - instance - test + +package-name = collective.limitfilesizepanel +package-extras = [test] + +parts += coverage test-coverage code-analysis dependencychecker -develop = . -sources-dir = extras -auto-checkout = - -[instance] -recipe = plone.recipe.zope2instance -user = admin:admin -http-address = 8080 -eggs = - Plone - Pillow - plone.app.debugtoolbar - collective.limitfilesizepanel[test] - -[test] -recipe = zc.recipe.testrunner -eggs = ${instance:eggs} -initialization = - os.environ['TZ'] = 'UTC' -defaults = ['-s', 'collective.limitfilesizepanel', '--auto-color', '--auto-progress'] - [coverage] recipe = zc.recipe.egg eggs = coverage diff --git a/collective/limitfilesizepanel/browser/configure.zcml b/collective/limitfilesizepanel/browser/configure.zcml index 7ec00d5..2175581 100644 --- a/collective/limitfilesizepanel/browser/configure.zcml +++ b/collective/limitfilesizepanel/browser/configure.zcml @@ -2,6 +2,7 @@ xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:plone="http://namespaces.plone.org/plone" + xmlns:zcml="http://namespaces.zope.org/zcml" i18n_domain="collective.limitfilesizepanel"> @@ -14,6 +15,7 @@ name="limitfilesize-settings" for="Products.CMFPlone.interfaces.IPloneSiteRoot" class=".controlpanel.LimitFileSizeControlPanel" + layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer" permission="collective.limitfilesizepanel.LimitFileSizePanel" /> @@ -25,4 +27,13 @@ layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer" allowed_interface=".limitfilesizepanel_view.IHelpersView" /> + + diff --git a/collective/limitfilesizepanel/browser/limitfilesizepanel_view.py b/collective/limitfilesizepanel/browser/limitfilesizepanel_view.py index f24c2e3..f70ebe2 100644 --- a/collective/limitfilesizepanel/browser/limitfilesizepanel_view.py +++ b/collective/limitfilesizepanel/browser/limitfilesizepanel_view.py @@ -1,13 +1,16 @@ # -*- coding: utf-8 -*- -from AccessControl import getSecurityManager from Acquisition import aq_base +from collective.limitfilesizepanel import messageFactory as _ from collective.limitfilesizepanel.interfaces import ILimitFileSizePanel from plone import api from plone.api.exc import InvalidParameterError from Products.Five.browser import BrowserView -from zope.interface import implements +from Products.validation.i18n import safe_unicode +from zope.i18n import translate +from zope.interface import implementer from zope.interface import Interface from ZPublisher.HTTPRequest import FileUpload + try: from plone.namedfile.interfaces import INamedBlobImageField from plone.namedfile.interfaces import INamedBlobFileField @@ -24,6 +27,9 @@ def checkSize(uploadfile, maxsize): def canBypassValidation(): """Check if the user has bypass permission""" + def newDataOnly(): + """ Return if the validation is only for new data """ + def get_maxsize(validator, field, **kwargs): """ This is the method called from AT validator @@ -42,11 +48,18 @@ def get_maxsize_tiny(metatypes): """ +@implementer(IHelpersView) class View(BrowserView): - ''' + """ Helper view for file uploads - ''' - implements(IHelpersView) + """ + + def newDataOnly(self): + """ + """ + return api.portal.get_registry_record( + 'new_data_only', + interface=ILimitFileSizePanel) def check_size_dx(self, uploadfile, maxsize): """ check the size of given file """ @@ -56,28 +69,32 @@ def check_size_dx(self, uploadfile, maxsize): } if self.canBypassValidation(): return result - - new_data_only = api.portal.get_registry_record( - 'new_data_only', - interface=ILimitFileSizePanel) - - if new_data_only and self.context != api.portal.get(): - # we are in edit, and we don't want validate uploaded files. - return result - - size = float(uploadfile.getSize()) + if (isinstance(uploadfile, FileUpload) or hasattr(aq_base(uploadfile), 'tell')): # NOQA + uploadfile.seek(0, 2) # eof + size = float(uploadfile.tell()) + uploadfile.seek(0) + else: + size = float(uploadfile.getSize()) sizeMB = (size / (1024 * 1024)) result['sizeMB'] = sizeMB - if sizeMB > maxsize: result['valid'] = False + msg = _( + 'validation_error', + default=u'Validation failed. Uploaded data is too large:' + u' ${size}MB (max ${max}MB)', + mapping={ + 'size': safe_unicode('{0:.1f}'.format(sizeMB)), + 'max': safe_unicode('{0:.1f}'.format(maxsize)) + }) + result['error'] = translate(msg, context=self.request) return result def check_size(self, uploadfile, maxsize): """ check the size of given file """ result = { 'maxsize': maxsize, - 'valid': True + 'valid': True, } if self.canBypassValidation(): @@ -108,14 +125,24 @@ def check_size(self, uploadfile, maxsize): if sizeMB > maxsize: result['valid'] = False + msg = _( + 'validation_error', + default=u'Validation failed. Uploaded data is too large:' + u' ${size}MB (max ${max}MB)', + mapping={ + 'size': safe_unicode('{0:.1f}'.format(sizeMB)), + 'max': safe_unicode('{0:.1f}'.format(maxsize)) + }) + result['error'] = translate(msg, context=self.request) return result def canBypassValidation(self): """Check if the user has bypass permission""" - sm = getSecurityManager() - return sm.checkPermission( - "collective.limitfilesizepanel: Bypass limit size", self.context) + return api.user.has_permission( + 'collective.limitfilesizepanel: Bypass limit size', + obj=self.context + ) def _get_type_maxsize(self, field, context): """Get portal_type/fieldname pair configuration in the registry""" @@ -201,9 +228,9 @@ def get_maxsize(self, validator, **kwargs): # get original max size if 'maxsize' in kwargs: maxsize = kwargs.get('maxsize') - elif hasattr(aq_base(instance), 'getMaxSizeFor'): + elif hasattr(aq_base(instance), 'getMaxSizeFor'): # noqa maxsize = instance.getMaxSizeFor(field.getName()) - elif hasattr(field, 'maxsize'): + elif hasattr(field, 'maxsize'): # noqa maxsize = field.maxsize else: # set to given default value (default defaults to 0) diff --git a/collective/limitfilesizepanel/browser/tinymce_upload_p5.py b/collective/limitfilesizepanel/browser/tinymce_upload_p5.py new file mode 100644 index 0000000..a9691fb --- /dev/null +++ b/collective/limitfilesizepanel/browser/tinymce_upload_p5.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from plone import api +from plone.app.content.browser.file import FileUploadView as BaseFileUploadView + +import mimetypes + + +class FileUpload(BaseFileUploadView): + """ + add filesize validation to tinymce file upload view + """ + + def __call__(self): + filedata = self.request.form.get('file', None) + if not filedata: + return super(FileUpload, self).__call__() + filename = filedata.filename + content_type = mimetypes.guess_type(filename)[0] or '' + ctr = api.portal.get_tool(name='content_type_registry') + portal_type = ctr.findTypeName( + filename.lower(), content_type, '') or 'File' + + helper_view = api.content.get_view( + name='lfsp_helpers_view', + context=self.context, + request=self.context.REQUEST,) + + if helper_view.newDataOnly() and '/edit' in self.request.get('HTTP_REFERER'): # noqa + return super(FileUpload, self).__call__() + maxsize = helper_view.get_maxsize_tiny((portal_type,)) + if not maxsize: + return super(FileUpload, self).__call__() + + size_check = helper_view.check_size_dx( + maxsize=maxsize, + uploadfile=filedata + ) + if size_check and not size_check.get('valid', False): + response = self.request.RESPONSE + response.setStatus(403) + return size_check.get('error', '') + return super(FileUpload, self).__call__() diff --git a/collective/limitfilesizepanel/configure.zcml b/collective/limitfilesizepanel/configure.zcml index ebb1413..8e70da0 100644 --- a/collective/limitfilesizepanel/configure.zcml +++ b/collective/limitfilesizepanel/configure.zcml @@ -79,7 +79,8 @@ /> - + + diff --git a/collective/limitfilesizepanel/dx_validators.py b/collective/limitfilesizepanel/dx_validators.py index 49b1b6d..37b26db 100644 --- a/collective/limitfilesizepanel/dx_validators.py +++ b/collective/limitfilesizepanel/dx_validators.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- -from Products.validation.i18n import safe_unicode from plone.namedfile.interfaces import INamedBlobImageField from plone.namedfile.interfaces import INamedBlobFileField -from collective.limitfilesizepanel import messageFactory as _ from zope.interface import Invalid from z3c.form import validator from plone import api from plone.api.exc import InvalidParameterError -from zope.i18n import translate +from plone.dexterity.browser.edit import DefaultEditForm class DXFileSizeValidator(validator.FileUploadValidator): @@ -18,7 +16,10 @@ class DXFileSizeValidator(validator.FileUploadValidator): def validate(self, value): super(DXFileSizeValidator, self).validate(value) if not value: - return + return True + + if isinstance(self.view, DefaultEditForm): + return True try: helper_view = api.content.get_view( name='lfsp_helpers_view', @@ -27,10 +28,13 @@ def validate(self, value): ) except InvalidParameterError: #  the view is enabled only when the product is installed - return - if helper_view.canBypassValidation(): return True - maxsize = helper_view.get_maxsize_dx(self, self.field) + if helper_view.newDataOnly() and isinstance(self.view, DefaultEditForm): # noqa + return True + maxsize = helper_view.get_maxsize_dx( + validator=self, + field=self.field + ) if not maxsize: return True @@ -39,20 +43,20 @@ def validate(self, value): uploadfile=value) if size_check and not size_check.get('valid', False): - msg = _( - 'validation_error', - default=u"Validation failed. Uploaded data is too large:" - u" ${size}MB (max ${max}MB)", - mapping={ - 'size': safe_unicode("%.1f" % size_check.get('sizeMB')), - 'max': safe_unicode("%.1f" % size_check.get('maxsize')) - }) - raise Invalid(translate(msg, context=self.context.REQUEST)) + raise Invalid(size_check.get('error', '')) return True -validator.WidgetValidatorDiscriminators(DXFileSizeValidator, +class ImageFileSizeValidator(DXFileSizeValidator): + """ """ + + +class FileSizeValidator(DXFileSizeValidator): + """ """ + + +validator.WidgetValidatorDiscriminators(ImageFileSizeValidator, field=INamedBlobImageField) -validator.WidgetValidatorDiscriminators(DXFileSizeValidator, +validator.WidgetValidatorDiscriminators(FileSizeValidator, field=INamedBlobFileField) diff --git a/collective/limitfilesizepanel/overrides.zcml b/collective/limitfilesizepanel/overrides.zcml index c640efb..9faec37 100644 --- a/collective/limitfilesizepanel/overrides.zcml +++ b/collective/limitfilesizepanel/overrides.zcml @@ -23,12 +23,12 @@ diff --git a/collective/limitfilesizepanel/tinymce_upload.py b/collective/limitfilesizepanel/tinymce_upload_p4.py similarity index 99% rename from collective/limitfilesizepanel/tinymce_upload.py rename to collective/limitfilesizepanel/tinymce_upload_p4.py index daae22b..544d642 100644 --- a/collective/limitfilesizepanel/tinymce_upload.py +++ b/collective/limitfilesizepanel/tinymce_upload_p4.py @@ -10,6 +10,8 @@ from Products.TinyMCE.adapters.Upload import Upload as BaseUpload from Products.validation.i18n import safe_unicode from zExceptions import BadRequest + +import logging import pkg_resources try: @@ -28,12 +30,14 @@ from zope.i18nmessageid import MessageFactory _ = MessageFactory('plone.tinymce') -import logging + logger = logging.getLogger(__name__) class Upload(BaseUpload): - + """ + P4 patch + """ def upload(self): # NOQA """Adds uploaded file""" context = aq_inner(self.context) diff --git a/docs/HISTORY.rst b/docs/HISTORY.rst index a2f5250..bc48324 100644 --- a/docs/HISTORY.rst +++ b/docs/HISTORY.rst @@ -1,10 +1,13 @@ Changelog ========= -2.0.4 (unreleased) +2.1.0 (unreleased) ------------------ -- Nothing changed yet. +- Fix validators for Dexterity fields. Now works well with Files and images + [cekk] +- Add support for Tinymce validation also for Plone 5 + [cekk] 2.0.3 (2018-01-30) diff --git a/plone-4.3.x.cfg b/plone-4.3.x.cfg deleted file mode 100644 index 36b2240..0000000 --- a/plone-4.3.x.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[buildout] -extends = - base.cfg - http://dist.plone.org/release/4.3-latest/versions.cfg - versions.cfg diff --git a/plone-5.0.x.cfg b/plone-5.0.x.cfg deleted file mode 100644 index 3997cd8..0000000 --- a/plone-5.0.x.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[buildout] -extends = - base.cfg - http://dist.plone.org/release/5.0.7/versions.cfg - versions.cfg diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..78606b7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +setuptools==38.2.4 +zc.buildout diff --git a/setup.py b/setup.py index b50ff4b..f2c99c7 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = '2.0.4.dev0' +version = '2.1.0.dev0' tests_require = [ 'plone.app.testing', diff --git a/travis.cfg b/travis.cfg deleted file mode 100644 index e800297..0000000 --- a/travis.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[buildout] -extends = plone-x.x.x.cfg -parts += createcoverage - -# caches, see also .travis.yaml -# one should not depend on '/home/travis' but it seems stable in containers. -eggs-directory = /home/travis/buildout-cache/eggs -download-cache = /home/travis/buildout-cache/downloads - -[createcoverage] -recipe = zc.recipe.egg -eggs = createcoverage - -[code-analysis] -recipe = plone.recipe.codeanalysis -pre-commit-hook = False -return-status-codes = True diff --git a/versions.cfg b/versions.cfg index 382aec5..0fae0db 100644 --- a/versions.cfg +++ b/versions.cfg @@ -1,23 +1,55 @@ [versions] # Build +setuptools = 38.5.1 +zc.buildout = +zope.configuration = 4.1.0 +plone.testing = +zExceptions = 3.6.1 setuptools = zc.buildout = -zc.recipe.egg = 2.0.3 - -zope.interface = 4.0.5 +mr.developer = >=1.38 +PyYAML = 3.12 +argh = 0.26.2 +args = 0.1.0 +check-manifest = 0.35 +clint = 0.5.1 +colorama = 0.3.7 +configparser = 3.5.0 coverage = 3.7.1 -flake8 = 2.4.0 -flake8-coding = 1.2.2 +createcoverage = 1.5 +enum34 = 1.1.6 +flake8 = 3.3.0 +flake8-blind-except = 0.1.1 +flake8-coding = 1.3.0 +flake8-debugger = 1.4.0 +flake8-deprecated = 1.1 +flake8-isort = 2.1.3 +flake8-pep3101 = 1.0 +flake8-plone-api = 1.2 +flake8-plone-hasattr = 0.1 +flake8-polyfill = 1.0.1 +flake8-print = 2.0.2 +flake8-quotes = 0.9.0 +flake8-string-format = 0.2.3 +flake8-todo = 0.7 +i18ndude = 4.0.1 +isort = 4.2.5 +mccabe = 0.6.1 +pathtools = 0.1.2 +pkginfo = 1.4.1 plone.recipe.codeanalysis = 2.2 - -Sphinx = 1.3.1 -docutils = 0.12 -Pygments = 2.0 -sphinxcontrib-httpexample = 0.5.2 - -zest.releaser = 6.7 +plone.testing = 5.0.0 +pycodestyle = 2.3.1 +pyflakes = 1.5.0 +requests-toolbelt = 0.7.1 +robotframework = 3.0 +robotframework-ride = 1.5.2.1 +robotframework-selenium2library = 1.7.4 +robotsuite = 1.7.0 +selenium = 2.53.6 +testfixtures = 4.13.4 twine = 1.8.1 -requests = 2.11.1 - -plone.testing = -zope.configuration = 4.0.3 +watchdog = 0.8.3 +z3c.jbot = 0.7.2 +zc.recipe.egg = 2.0.3 +zest.releaser = 6.9