misc: automatically resize profile image (#27644)
This commit is contained in:
parent
a5d652ce81
commit
90ff2d46ab
|
@ -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'),
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue