misc: automatically resize profile image (#27644)

This commit is contained in:
Frédéric Péters 2018-10-29 15:35:10 +01:00
parent a5d652ce81
commit 90ff2d46ab
4 changed files with 42 additions and 24 deletions

View File

@ -145,7 +145,7 @@ default_settings = dict(
A2_OPENED_SESSION_COOKIE_NAME=Setting(default='A2_OPENED_SESSION', definition='Authentic session open'),
A2_OPENED_SESSION_COOKIE_DOMAIN=Setting(default=None),
A2_ATTRIBUTE_KINDS=Setting(default=(), definition='List of other attribute kinds'),
A2_ATTRIBUTE_KIND_PROFILE_IMAGE_MAX_SIZE=Setting(default=200, definition='Max width and height for a profile image'),
A2_ATTRIBUTE_KIND_PROFILE_IMAGE_SIZE=Setting(default=200, definition='Width and height for a profile image'),
A2_VALIDATE_EMAIL=Setting(default=False, definition='Validate user email server by doing an RCPT command'),
A2_VALIDATE_EMAIL_DOMAIN=Setting(default=True, definition='Validate user email domain'),
A2_PASSWORD_POLICY_MIN_CLASSES=Setting(default=3, definition='Minimum number of characters classes to be present in passwords'),

View File

@ -46,16 +46,9 @@ class CheckPasswordField(CharField):
class ProfileImageField(FileField):
widget = ProfileImageInput
def __init__(self, *args, **kwargs):
kwargs.setdefault(
'help_text',
_('Image must be JPG or PNG of size less '
'than {max_size}x{max_size} pixels').format(max_size=self.max_size))
super(ProfileImageField, self).__init__(*args, **kwargs)
@property
def max_size(self):
return app_settings.A2_ATTRIBUTE_KIND_PROFILE_IMAGE_MAX_SIZE
def image_size(self):
return app_settings.A2_ATTRIBUTE_KIND_PROFILE_IMAGE_SIZE
def clean(self, data, initial=None):
if data is FILE_INPUT_CONTRADICTION or data is False or data is None:
@ -66,11 +59,7 @@ class ProfileImageField(FileField):
image = PIL.Image.open(io.BytesIO(data.read()))
except (IOError, PIL.Image.DecompressionBombWarning):
raise ValidationError(_('The image is not valid'))
width, height = image.size
max_size = app_settings.A2_ATTRIBUTE_KIND_PROFILE_IMAGE_MAX_SIZE
if width > max_size or height > max_size:
raise ValidationError(_('The image is bigger than {max_size}x{max_size} pixels')
.format(max_size=self.max_size))
image = self.normalize_image(image)
new_data = self.file_from_image(image, data.name)
return super(ProfileImageField, self).clean(new_data, initial=initial)
@ -85,3 +74,24 @@ class ProfileImageField(FileField):
optimize=1)
output.seek(0)
return File(output, name=name)
def normalize_image(self, image):
width = height = self.image_size
if abs((1.0 * width / height) - (1.0 * image.size[0] / image.size[1])) > 0.1:
# aspect ratio change, crop the image first
box = [0, 0, image.size[0], int(image.size[0] * (1.0 * height / width))]
if box[2] > image.size[0]:
box = [int(t * (1.0 * image.size[0] / box[2])) for t in box]
if box[3] > image.size[1]:
box = [int(t * (1.0 * image.size[1] / box[3])) for t in box]
if image.size[0] > image.size[1]: # landscape
box[0] = (image.size[0] - box[2]) / 2 # keep the middle
box[2] += box[0]
else:
box[1] = (image.size[1] - box[3]) / 4 # keep mostly the top
box[3] += box[1]
image = image.crop(box)
return image.resize([width, height], PIL.Image.ANTIALIAS)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 795 B

View File

@ -1,5 +1,10 @@
# -*- coding: utf-8 -*-
import datetime
import os
import PIL.Image
from django.conf import settings
from authentic2.custom_user.models import User
from authentic2.models import Attribute
@ -398,14 +403,6 @@ def test_profile_image(db, app, admin, mailoutbox, media):
response = form.submit()
assert response.pyquery.find('.form-field-error #id_cityscape_image')
# verify 201x201 image is refused
form = response.form
form.set('cityscape_image', Upload('tests/201x201.jpg'))
form.set('password1', '12345abcdA')
form.set('password2', '12345abcdA')
response = form.submit()
assert response.pyquery.find('.form-field-error #id_cityscape_image')
# verify 200x200 image is accepted
form = response.form
form.set('cityscape_image', Upload('tests/200x200.jpg'))
@ -413,6 +410,7 @@ def test_profile_image(db, app, admin, mailoutbox, media):
form.set('password2', '12345abcdA')
response = form.submit()
assert john().attributes.cityscape_image
profile_filename = john().attributes.cityscape_image.name
# verify API serves absolute URL for profile images
app.authorization = ('Basic', (admin.username, admin.username))
@ -429,7 +427,17 @@ def test_profile_image(db, app, admin, mailoutbox, media):
response = form.submit()
assert john().attributes.cityscape_image == None
# verify API serves absolute URL for profile images
# verify API serves None for empty profile images
app.authorization = ('Basic', (admin.username, admin.username))
response = app.get('/api/users/%s/' % john().uuid)
assert response.json['cityscape_image'] is None
# verify 201x201 image is accepted and resized
response = app.get('/accounts/edit/')
form = response.form
form.set('edit-profile-cityscape_image', Upload('tests/201x201.jpg'))
response = form.submit()
image = PIL.Image.open(open(os.path.join(settings.MEDIA_ROOT, john().attributes.cityscape_image.name)))
assert image.width == 200
assert image.height == 200
assert john().attributes.cityscape_image.name != profile_filename