122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2019 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import io
|
|
import warnings
|
|
|
|
import PIL.Image
|
|
from django.core.files import File
|
|
from django.forms import CharField, EmailField, FileField, ValidationError
|
|
from django.forms.fields import FILE_INPUT_CONTRADICTION
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from authentic2 import app_settings
|
|
from authentic2.forms.widgets import (
|
|
CheckPasswordInput,
|
|
EmailInput,
|
|
NewPasswordInput,
|
|
PasswordInput,
|
|
ProfileImageInput,
|
|
)
|
|
from authentic2.passwords import password_help_text, validate_password
|
|
from authentic2.validators import email_validator
|
|
|
|
|
|
class PasswordField(CharField):
|
|
widget = PasswordInput
|
|
|
|
|
|
class NewPasswordField(CharField):
|
|
widget = NewPasswordInput
|
|
default_validators = [validate_password]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['help_text'] = password_help_text()
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
class CheckPasswordField(CharField):
|
|
widget = CheckPasswordInput
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs[
|
|
'help_text'
|
|
] = '''
|
|
<span class="a2-password-check-equality-default">%(default)s</span>
|
|
<span class="a2-password-check-equality-matched">%(match)s</span>
|
|
<span class="a2-password-check-equality-unmatched">%(nomatch)s</span>
|
|
''' % {
|
|
'default': _('Both passwords must match.'),
|
|
'match': _('Passwords match.'),
|
|
'nomatch': _('Passwords do not match.'),
|
|
}
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
class ProfileImageField(FileField):
|
|
widget = ProfileImageInput
|
|
|
|
@property
|
|
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:
|
|
return super().clean(data, initial=initial)
|
|
# we have a file
|
|
try:
|
|
with warnings.catch_warnings():
|
|
image = PIL.Image.open(io.BytesIO(data.read()))
|
|
except (OSError, PIL.Image.DecompressionBombWarning):
|
|
raise ValidationError(_('The image is not valid'))
|
|
image = self.normalize_image(image)
|
|
new_data = self.file_from_image(image, data.name)
|
|
return super().clean(new_data, initial=initial)
|
|
|
|
def file_from_image(self, image, name=None):
|
|
output = io.BytesIO()
|
|
if image.mode != 'RGB':
|
|
image = image.convert('RGB')
|
|
image.save(output, format='JPEG', quality=99, 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)
|
|
|
|
|
|
class ValidatedEmailField(EmailField):
|
|
default_validators = [email_validator]
|
|
widget = EmailInput
|