blender-id/bid_main/fields.py
Anna Sirota ba1f69eced Avatars: replace image column with the new one
From this change onwards only pre-generated avatar thumbnails
are used anywhere in Blender ID.
No implicit on-demand thumbnail generation or caching (or trashing of
the cache table on each lookup of a thumbnail) should be going on.
2023-12-14 13:02:44 +01:00

100 lines
3.2 KiB
Python

"""Custom form and model fields."""
from io import BytesIO
import sys
import xml.etree.cElementTree as et
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator, get_available_image_extensions
from django.db import models
from django.db.models.fields.files import ImageFieldFile
from django.forms import ImageField
import sorl.thumbnail
class AvatarFieldFile(ImageFieldFile): # keeping it here because it's used in migrations
pass
class AvatarField(sorl.thumbnail.ImageField):
"""Sorl Thumbnail for user avatars."""
attr_class = AvatarFieldFile
def validate_image_and_svg_file_extension(value):
"""Allow images and SVG."""
allowed_extensions = get_available_image_extensions() + ['svg']
return FileExtensionValidator(allowed_extensions=allowed_extensions)(value)
class SVGAndImageFormField(ImageField):
"""Validate uploaded file and allow images and SVG."""
default_validators = [validate_image_and_svg_file_extension]
def to_python(self, data):
"""Do what ImageField does, then check for SVG and allow it."""
f = super(ImageField, self).to_python(data)
if f is None:
return None
# Do whatever ImageField does first
from PIL import Image
# We need to get a file object for Pillow. We might have a path or we might
# have to read the data into memory.
if hasattr(data, 'temporary_file_path'):
file = data.temporary_file_path()
else:
if hasattr(data, 'read'):
file = BytesIO(data.read())
else:
file = BytesIO(data['content'])
try:
# load() could spot a truncated JPEG, but it loads the entire
# image in memory, which is a DoS vector. See #3848 and #18520.
image = Image.open(file)
# verify() must be called immediately after the constructor.
image.verify()
# Annotating so subclasses can reuse it for their own validation
f.image = image
# Pillow doesn't detect the MIME type of all formats. In those
# cases, content_type will be None.
f.content_type = Image.MIME.get(image.format)
except Exception:
# Add a workaround to handle SVG images
if not self.is_svg(file):
raise ValidationError(
self.error_messages["invalid_image"], code="invalid_image"
).with_traceback(sys.exc_info()[2])
if hasattr(f, "seek") and callable(f.seek):
f.seek(0)
return f
def is_svg(self, f):
"""Check if provided file is svg."""
f.seek(0)
tag = None
try:
for event, el in et.iterparse(f, ("start",)):
tag = el.tag
break
except et.ParseError:
pass
return tag == "{http://www.w3.org/2000/svg}svg"
class SVGAndImageField(models.ImageField):
"""Override validation with SVGAndImageFormField."""
def formfield(self, **kwargs):
return super().formfield(
**{
'form_class': SVGAndImageFormField,
**kwargs,
}
)