''' views under / ''' from io import BytesIO from os import path import hashlib from urllib.request import urlopen from urllib.error import HTTPError, URLError from ssl import SSLError from django.views.generic.base import TemplateView, View from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_lazy as _ from django.urls import reverse_lazy from PIL import Image from monsterid.id import build_monster as BuildMonster import Identicon from pydenticon5 import Pydenticon5 import pagan from robohash import Robohash from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY, DEFAULT_AVATAR_SIZE from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId from . ivataraccount.models import pil_format, file_format URL_TIMEOUT = 5 # in seconds def get_size(request, size=DEFAULT_AVATAR_SIZE): ''' Get size from the URL arguments ''' sizetemp = None if 's' in request.GET: sizetemp = request.GET['s'] if 'size' in request.GET: sizetemp = request.GET['size'] if sizetemp: if sizetemp != '' and sizetemp is not None and sizetemp != '0': try: if int(sizetemp) > 0: size = int(sizetemp) # Should we receive something we cannot convert to int, leave # the user with the default value of 80 except ValueError: pass if size > int(AVATAR_MAX_SIZE): size = int(AVATAR_MAX_SIZE) return size class AvatarImageView(TemplateView): ''' View to return (binary) image, based on OpenID/Email (both by digest) ''' # TODO: Do cache resize images!! Memcached? def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-return-statements ''' Override get from parent class ''' model = ConfirmedEmail size = get_size(request) imgformat = 'png' obj = None default = None forcedefault = False gravatarredirect = False gravatarproxy = True if 'd' in request.GET: default = request.GET['d'] if 'default' in request.GET: default = request.GET['default'] if 'f' in request.GET: if request.GET['f'] == 'y': forcedefault = True if 'forcedefault' in request.GET: if request.GET['forcedefault'] == 'y': forcedefault = True if 'gravatarredirect' in request.GET: if request.GET['gravatarredirect'] == 'y': gravatarredirect = True if 'gravatarproxy' in request.GET: if request.GET['gravatarproxy'] == 'n': gravatarproxy = False try: obj = model.objects.get(digest=kwargs['digest']) except ObjectDoesNotExist: try: obj = model.objects.get(digest_sha256=kwargs['digest']) except ObjectDoesNotExist: model = ConfirmedOpenId try: obj = model.objects.get(digest=kwargs['digest']) except: pass # If that mail/openid doesn't exist, or has no photo linked to it if not obj or not obj.photo or forcedefault: gravatar_url = 'https://secure.gravatar.com/avatar/' + kwargs['digest'] \ + '?s=%i' % size # If we have redirection to Gravatar enabled, this overrides all # default= settings, except forcedefault! if gravatarredirect and not forcedefault: return HttpResponseRedirect(gravatar_url) # Request to proxy Gravatar image - only if not forcedefault if gravatarproxy and not forcedefault: url = reverse_lazy('gravatarproxy', args=[kwargs['digest']]) \ + '?s=%i' % size + '&default=%s' % default return HttpResponseRedirect(url) # Return the default URL, as specified, or 404 Not Found, if default=404 if default: # Proxy to gravatar to generate wavatar - lazy me if str(default) == 'wavatar': url = reverse_lazy('gravatarproxy', args=[kwargs['digest']]) \ + '?s=%i' % size + '&default=%s&f=y' % default return HttpResponseRedirect(url) if str(default) == str(404): return HttpResponseNotFound(_('

Image not found

')) if str(default) == 'monsterid': monsterdata = BuildMonster(seed=kwargs['digest'], size=(size, size)) data = BytesIO() monsterdata.save(data, 'PNG', quality=JPEG_QUALITY) data.seek(0) return HttpResponse( data, content_type='image/png') if str(default) == 'robohash': roboset = 'any' if request.GET.get('robohash'): roboset = request.GET.get('robohash') robohash = Robohash(kwargs['digest']) robohash.assemble(roboset=roboset, sizex=size, sizey=size) data = BytesIO() robohash.img.save(data, format='png') data.seek(0) return HttpResponse( data, content_type='image/png') if str(default) == 'retro': identicon = Identicon.render(kwargs['digest']) data = BytesIO() img = Image.open(BytesIO(identicon)) img = img.resize((size, size), Image.ANTIALIAS) img.save(data, 'PNG', quality=JPEG_QUALITY) data.seek(0) return HttpResponse( data, content_type='image/png') if str(default) == 'pagan': paganobj = pagan.Avatar(kwargs['digest']) data = BytesIO() img = paganobj.img.resize((size, size), Image.ANTIALIAS) img.save(data, 'PNG', quality=JPEG_QUALITY) data.seek(0) return HttpResponse( data, content_type='image/png') if str(default) == 'identicon': p = Pydenticon5() # In order to make use of the whole 32 bytes digest, we need to redigest them. newdigest = hashlib.md5(bytes(kwargs['digest'], 'utf-8')).hexdigest() img = p.draw(newdigest, size, 0) data = BytesIO() img.save(data, 'PNG', quality=JPEG_QUALITY) data.seek(0) return HttpResponse( data, content_type='image/png') if str(default) == 'mm' or str(default) == 'mp': # If mm is explicitly given, we need to catch that static_img = path.join('static', 'img', 'mm', '%s%s' % (str(size), '.png')) if not path.isfile(static_img): # We trust this exists!!! static_img = path.join('static', 'img', 'mm', '512.png') # We trust static/ is mapped to /static/ return HttpResponseRedirect('/' + static_img) return HttpResponseRedirect(default) static_img = path.join('static', 'img', 'nobody', '%s%s' % (str(size), '.png')) if not path.isfile(static_img): # We trust this exists!!! static_img = path.join('static', 'img', 'nobody', '512.png') # We trust static/ is mapped to /static/ return HttpResponseRedirect('/' + static_img) imgformat = obj.photo.format photodata = Image.open(BytesIO(obj.photo.data)) # If the image is smaller than what was requested, we need # to use the function resize if photodata.size[0] < size or photodata.size[1] < size: photodata = photodata.resize((size, size), Image.ANTIALIAS) else: photodata.thumbnail((size, size), Image.ANTIALIAS) data = BytesIO() photodata.save(data, pil_format(imgformat), quality=JPEG_QUALITY) data.seek(0) obj.photo.access_count += 1 obj.photo.save() obj.access_count += 1 obj.save() return HttpResponse( data, content_type='image/%s' % imgformat) class GravatarProxyView(View): ''' Proxy request to Gravatar and return the image from there ''' # TODO: Do cache images!! Memcached? def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,no-self-use,unused-argument ''' Override get from parent class ''' def redir_default(default=None): url = reverse_lazy( 'avatar_view', args=[kwargs['digest']]) + '?s=%i' % size + '&forcedefault=y' if default != None: url += '&default=%s' % default return HttpResponseRedirect(url) size = get_size(request) gravatarimagedata = None default = None try: if str(request.GET['default']) != 'None': default = request.GET['default'] except: pass if str(default) != 'wavatar': # This part is special/hackish # Check if the image returned by Gravatar is their default image, if so, # redirect to our default instead. gravatar_test_url = 'https://secure.gravatar.com/avatar/' + kwargs['digest'] \ + '?s=%i' % 50 try: testdata = urlopen(gravatar_test_url, timeout=URL_TIMEOUT) data = BytesIO(testdata.read()) if hashlib.md5(data.read()).hexdigest() == '71bc262d627971d13fe6f3180b93062a': return redir_default(default) except Exception as exc: print('Gravatar test url fetch failed: %s' % exc) gravatar_url = 'https://secure.gravatar.com/avatar/' + kwargs['digest'] \ + '?s=%i' % size + '&d=%s' % default try: gravatarimagedata = urlopen(gravatar_url, timeout=URL_TIMEOUT) except HTTPError as exc: if exc.code != 404 and exc.code != 503: print( 'Gravatar fetch failed with an unexpected %s HTTP error' % exc.code) return redir_default(default) except URLError as exc: print( 'Gravatar fetch failed with URL error: %s' % exc.reason) return redir_default(default) except SSLError as exc: print( 'Gravatar fetch failed with SSL error: %s' % exc.reason) return redir_default(default) try: data = BytesIO(gravatarimagedata.read()) img = Image.open(data) data.seek(0) return HttpResponse( data.read(), content_type='image/%s' % file_format(img.format)) except ValueError as exc: print('Value error: %s' % exc) return redir_default(default) # We shouldn't reach this point... But make sure we do something return redir_default(default)