Skip to content

Commit

Permalink
Handle the massive upload
Browse files Browse the repository at this point in the history
  • Loading branch information
folix-01 committed Jan 31, 2024
1 parent b81863f commit 9ded488
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 67 deletions.
5 changes: 5 additions & 0 deletions buildout.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
[buildout]


# use this extend one of the buildout configuration:
extends =
test_plone52.cfg

[instance]
eggs +=
redturtle.volto
50 changes: 34 additions & 16 deletions collective/limitfilesizepanel/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,55 @@
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">
i18n_domain="collective.limitfilesizepanel"
>


<browser:resourceDirectory
name="collective.limitfilesizepanel.images"
directory="images"
/>
<browser:resourceDirectory
name="collective.limitfilesizepanel.images"
directory="images"
/>

<browser:page
name="limitfilesize-settings"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".controlpanel.LimitFileSizeControlPanel"
layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer"
permission="collective.limitfilesizepanel.LimitFileSizePanel"
/>
<browser:page
name="limitfilesize-settings"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".controlpanel.LimitFileSizeControlPanel"
permission="collective.limitfilesizepanel.LimitFileSizePanel"
layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer"
/>

<browser:page
<browser:page
name="lfsp_helpers_view"
for="*"
class=".limitfilesizepanel_view.View"
allowed_interface=".limitfilesizepanel_view.IHelpersView"
permission="zope2.View"
for="*"
layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer"
allowed_interface=".limitfilesizepanel_view.IHelpersView"
/>

<browser:page
zcml:condition="have plone-5"
name="fileUpload"
for="Products.CMFCore.interfaces._content.IFolderish"
class=".tinymce_upload_p5.FileUpload"
permission="zope2.View"
layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer"
zcml:condition="have plone-5"
/>

<browser:page
name="fileUpload"
for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot"
class=".folder_contents_upload.FileUpload"
permission="zope2.View"
layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer"
/>

<browser:page
name="fileUpload"
for="plone.dexterity.interfaces.IDexterityContainer"
class=".folder_contents_upload.FileUpload"
permission="zope2.View"
layer="collective.limitfilesizepanel.interfaces.ILimitFileSizePanelLayer"
/>

</configure>
47 changes: 47 additions & 0 deletions collective/limitfilesizepanel/browser/folder_contents_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from plone import api
from plone.app.content.browser.file import FileUploadView as BaseFileUploadView

import mimetypes


class FileUpload(BaseFileUploadView):
"""
Add filetype validation for folder_contents massive upload view.
This view is a merge from collective.limitfilesizepanel and rer.hardening
customizations.
"""

def __call__(self):
filedata = self.request.form.get("file", None)

if not filedata:
return super(FileUpload, self).__call__()

# limitfilesizepanel check
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(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__()
40 changes: 24 additions & 16 deletions collective/limitfilesizepanel/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,50 +1,58 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml"
xmlns:five="http://namespaces.zope.org/five"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:monkey="http://namespaces.plone.org/monkey"
i18n_domain="collective.limitfilesizepanel">
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:monkey="http://namespaces.plone.org/monkey"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="collective.limitfilesizepanel"
>

<five:registerPackage package="." />

<include package="plone.app.registry" />
<include package="collective.monkeypatcher" />

<i18n:registerTranslations directory="locales" />

<permission
id="collective.limitfilesizepanel.LimitFileSizePanel"
title="collective.limitfilesizepanel: Manage limit file size settings"
/>
id="collective.limitfilesizepanel.LimitFileSizePanel"
title="collective.limitfilesizepanel: Manage limit file size settings"
/>
<permission
id="collective.limitfilesizepanel.BypassLimitSize"
title="collective.limitfilesizepanel: Bypass limit size"
/>
id="collective.limitfilesizepanel.BypassLimitSize"
title="collective.limitfilesizepanel: Bypass limit size"
/>

<include package=".browser" />
<include file="upgrades.zcml" />


<genericsetup:registerProfile
name="default"
title="Limit files and images size"
directory="profiles/default"
description="Configure the file size limit from control panel"
provides="Products.GenericSetup.interfaces.EXTENSION"
directory="profiles/default"
/>
<genericsetup:registerProfile
name="uninstall"
title="Remove collective.limitfilesizepanel"
directory="profiles/uninstall"
description=""
provides="Products.GenericSetup.interfaces.EXTENSION"
directory="profiles/uninstall"
/>

<!-- <adapter factory=".dx_validators.FileSizeValidator" />
<!-- <adapter factory=".dx_validators.FileSizeValidator" />
<adapter factory=".dx_validators.ImageFileSizeValidator" /> -->
<adapter factory=".dx_validators.FileSizeValidator" name="collective.limitfilesizepanel.file_size_validator" />
<adapter factory=".dx_validators.ImageSizeValidator" name="collective.limitfilesizepanel.image_size_validator" />
<adapter
factory=".dx_validators.FileSizeValidator"
name="collective.limitfilesizepanel.file_size_validator"
/>
<adapter
factory=".dx_validators.ImageSizeValidator"
name="collective.limitfilesizepanel.image_size_validator"
/>



Expand Down
176 changes: 176 additions & 0 deletions collective/limitfilesizepanel/tests/test_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
from collective.limitfilesizepanel.interfaces import ILimitFileSizePanel
from collective.limitfilesizepanel.testing import LIMITFILESIZEPANEL_FUNCTIONAL_TESTING
from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from plone.registry.interfaces import IRegistry
from zope.component import queryUtility
from plone.namedfile.field import validate_image_field
from plone.namedfile.field import validate_file_field
from plone.namedfile.interfaces import INamedImageField
from plone.namedfile.interfaces import INamedFileField
from zope.interface import implementer
from plone.namedfile.file import NamedImage
from plone.namedfile.file import NamedFile
from io import BytesIO
from PIL import Image
from transaction import commit
from zope.interface.exceptions import Invalid

import unittest
import json


@implementer(INamedImageField)
class FakeImageField(object):
def __init__(self, name=""):
self.name = name

__name__ = "logo"

def getName(self):
return self.name


@implementer(INamedFileField)
class FakeFileField(object):
def __init__(self, name=""):
self.name = name

__name__ = "file"

def getName(self):
return self.name


class TestValidation(unittest.TestCase):
layer = LIMITFILESIZEPANEL_FUNCTIONAL_TESTING

def setUp(self):
self.portal = self.layer["portal"]
self.request = self.layer["request"]

setRoles(self.portal, TEST_USER_ID, ["Manager"])
self.registry = queryUtility(IRegistry)

api.portal.set_registry_record(
"new_data_only",
False,
interface=ILimitFileSizePanel,
)

api.portal.set_registry_record(
"file_size",
1,
interface=ILimitFileSizePanel,
)
api.portal.set_registry_record(
"image_size",
1,
interface=ILimitFileSizePanel,
)

api.portal.set_registry_record(
"types_settings",
json.dumps(
[{"content_type": "News Item", "field_name": "image", "size": 2}]
),
interface=ILimitFileSizePanel,
)
commit()

def generate_image(self, needed_size=1):
# Set the dimensions of the image
width = 1024
height = 1024
img_size = 1024 * 1024 * needed_size
# Create a new image with white background
image = Image.new("RGB", (width, height), (255, 255, 255))

# Save the image to an in-memory buffer as a JPEG file with maximum quality
buffer = BytesIO()
image.save(buffer, format="JPEG", quality=100)

# Get the size of the image buffer in bytes
size = buffer.tell()

# Ensure that the image buffer is exactly 1 MB
if size < img_size:
padding = b"\0" * (img_size - size)
buffer.write(padding)

return buffer

def test_validate_image_field(self):
# field is empty
field = FakeImageField()
field.context = self.portal

image = NamedImage()

# 1mb image is ok
image._setData(self.generate_image())
validate_image_field(field, image)

image._setData(self.generate_image(2))

with self.assertRaises(Invalid) as cm:
validate_image_field(field, image)

self.assertIn(
"Validation failed. Uploaded data is too large: 2.0MB (max 1.0MB)",
str(cm.exception),
)

def test_validate_file_field(self):
# field is empty
field = FakeFileField()
field.context = self.portal

example_file = NamedFile()
text = " " * (1024 * 1024)
example_file._setData(BytesIO(text.encode()))
# 1mb file is ok
validate_file_field(field, example_file)

text = " " * (1024 * 1024 * 2)
example_file._setData(BytesIO(text.encode()))

with self.assertRaises(Invalid) as cm:
validate_file_field(field, example_file)

self.assertIn(
"Validation failed. Uploaded data is too large: 2.0MB (max 1.0MB)",
str(cm.exception),
)

def test_validate_image_field_on_news(self):
news = api.content.create(
type="News Item",
id="news",
container=self.portal,
)

field = FakeImageField(name="image")
field.context = news
image = NamedImage()

# 1mb image is ok
image._setData(self.generate_image())
validate_image_field(field, image)

# 2mb image is ok
image._setData(self.generate_image(2))
validate_image_field(field, image)

# 3mb image is not ok
image._setData(self.generate_image(3))

with self.assertRaises(Invalid) as cm:
validate_image_field(field, image)

self.assertIn(
"Validation failed. Uploaded data is too large: 3.0MB (max 2.0MB)",
str(cm.exception),
)
Loading

0 comments on commit 9ded488

Please sign in to comment.