diff --git a/ivatar/ivataraccount/test_views.py b/ivatar/ivataraccount/test_views.py index 7c99489..45b7e59 100644 --- a/ivatar/ivataraccount/test_views.py +++ b/ivatar/ivataraccount/test_views.py @@ -1059,7 +1059,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods self.user.photo_set.first(), 'set_photo did not work!?') - def test_avatar_url_mail(self, do_upload_and_confirm=True): + def test_avatar_url_mail(self, do_upload_and_confirm=True, size=(80, 80)): ''' Test fetching avatar via mail ''' @@ -1068,18 +1068,21 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods self.test_confirm_email() urlobj = urlsplit( libravatar_url( - email=self.user.confirmedemail_set.first().email) + email=self.user.confirmedemail_set.first().email, + size=size[0], + ) ) - url = urlobj.path + url = '%s?%s' % (urlobj.path, urlobj.query) response = self.client.get(url, follow=True) self.assertEqual( response.status_code, 200, 'unable to fetch avatar?') + photodata = Image.open(BytesIO(response.content)) self.assertEqual( - response.content, - self.user.photo_set.first().data, - 'Why is this not the same data?') + photodata.size, + size, + 'Why is this not the correct size?') def test_avatar_url_openid(self): ''' @@ -1088,18 +1091,21 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods self.test_assign_photo_to_openid() urlobj = urlsplit( libravatar_url( - openid=self.user.confirmedopenid_set.first().openid) + openid=self.user.confirmedopenid_set.first().openid, + size=80, + ) ) - url = urlobj.path + url = '%s?%s' % (urlobj.path, urlobj.query) response = self.client.get(url, follow=True) self.assertEqual( response.status_code, 200, 'unable to fetch avatar?') + photodata = Image.open(BytesIO(response.content)) self.assertEqual( - response.content, - self.user.photo_set.first().data, - 'Why is this not the same data?') + photodata.size, + (80, 80), + 'Why is this not the correct size?') def test_avatar_url_inexisting_mail_digest(self): # pylint: disable=invalid-name ''' @@ -1109,14 +1115,20 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods self.test_confirm_email() urlobj = urlsplit( libravatar_url( - email=self.user.confirmedemail_set.first().email) + email=self.user.confirmedemail_set.first().email, + size=80, + ) ) # Simply delete it, then it digest is 'correct', but # the hash is no longer there self.user.confirmedemail_set.first().delete() - url = urlobj.path - self.assertRaises(Exception, lambda: - self.client.get(url, follow=True)) + url = '%s?%s' % (urlobj.path, urlobj.query) + response = self.client.get(url, follow=True) + self.assertEqual( + response['Content-Type'], + 'image/png', + 'Content type wrong!?') + # Eventually one should check if the data is the same def test_crop_photo(self): ''' @@ -1135,6 +1147,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods response.status_code, 200, 'unable to crop?') - self.test_avatar_url_mail(do_upload_and_confirm=False) + self.test_avatar_url_mail(do_upload_and_confirm=False, size=(20, 20)) img = Image.open(BytesIO(self.user.photo_set.first().data)) self.assertEqual(img.size, (20, 20), 'cropped to 20x20, but resulting image isn\'t 20x20!?') diff --git a/ivatar/ivataraccount/views.py b/ivatar/ivataraccount/views.py index 0ec9d17..c2be059 100644 --- a/ivatar/ivataraccount/views.py +++ b/ivatar/ivataraccount/views.py @@ -531,7 +531,7 @@ class CropPhotoView(TemplateView): model = Photo def get(self, request, *args, **kwargs): - photo = self.model.objects.get(pk=kwargs['pk'], user=request.user) + photo = self.model.objects.get(pk=kwargs['pk'], user=request.user) # pylint: disable=no-member email = request.GET.get('email') openid = request.GET.get('openid') return render(self.request, self.template_name, { @@ -541,7 +541,7 @@ class CropPhotoView(TemplateView): }) def post(self, request, *args, **kwargs): - photo = self.model.objects.get(pk=kwargs['pk'], user=request.user) + photo = self.model.objects.get(pk=kwargs['pk'], user=request.user) # pylint: disable=no-member dimensions = { 'x': int(request.POST['x']), 'y': int(request.POST['y']), diff --git a/ivatar/views.py b/ivatar/views.py index 1de9883..6e1ae03 100644 --- a/ivatar/views.py +++ b/ivatar/views.py @@ -1,29 +1,42 @@ ''' views under / ''' -import io +from io import BytesIO +from os import path +from PIL import Image from django.views.generic.base import TemplateView from django.http import HttpResponse -from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId from django.core.exceptions import ObjectDoesNotExist +from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY +from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId +from . ivataraccount.models import pil_format class AvatarImageView(TemplateView): ''' - View to return (binary) image, based for OpenID/Email (both by digest) + View to return (binary) image, based on OpenID/Email (both by digest) ''' + # TODO: Do cache resize images!! Memcached? def get(self, request, *args, **kwargs): ''' Override get from parent class ''' model = ConfirmedEmail + size = 80 + imgformat = 'png' + obj = None + if 's' in request.GET: + size = request.GET['s'] + size = int(size) + if size > int(AVATAR_MAX_SIZE): + size = int(AVATAR_MAX_SIZE) if len(kwargs['digest']) == 32: # Fetch by digest from mail pass elif len(kwargs['digest']) == 64: - if ConfirmedOpenId.objects.filter( - digest=kwargs['digest']).count(): # pylint: disable=no-member + if ConfirmedOpenId.objects.filter( # pylint: disable=no-member + digest=kwargs['digest']).count(): # Fetch by digest from OpenID model = ConfirmedOpenId else: # pragma: no cover @@ -36,15 +49,26 @@ class AvatarImageView(TemplateView): try: obj = model.objects.get(digest_sha256=kwargs['digest']) except ObjectDoesNotExist: - # TODO: Use default!? - raise Exception('Mail/openid ("%s") does not exist"' % - kwargs['digest']) - if not obj.photo: - # That is hacky, but achieves what we want :-) - attr = getattr(obj, 'email', obj.openid) - # TODO: Use default!? - raise Exception('No photo assigned to "%s"' % attr) + pass + + if not obj or not obj.photo: + static_img = path.join('static', 'img', 'mm', '%s%s' % (str(size), '.png')) + if path.isfile(static_img): + photodata = Image.open(static_img) + else: + # TODO: Resize it!? + static_img = path.join('static', 'img', 'mm', '512.png') + else: + imgformat = obj.photo.format + photodata = Image.open(BytesIO(obj.photo.data)) + + photodata.thumbnail((size, size), Image.ANTIALIAS) + data = BytesIO() + photodata.save(data, pil_format(imgformat), quality=JPEG_QUALITY) + data.seek(0) return HttpResponse( - io.BytesIO(obj.photo.data), - content_type='image/%s' % obj.photo.format) + data, + content_type='image/%s' % imgformat) + # One eventually also wants to check if the DATA is correct, + # not only the size