Files
ivatar/ivatar/views.py

330 lines
13 KiB
Python

'''
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 ivatar.settings import CACHE_IMAGES_MAX_AGE
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 options(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-return-statements
response = HttpResponse("", content_type='text/plain')
response['Allow'] = "404 mm mp retro pagan wavatar monsterid robohash identicon"
return response
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
# In case no digest at all is provided, return to home page
if not 'digest' in kwargs:
return HttpResponseRedirect(reverse_lazy('home'))
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(_('<h1>Image not found</h1>'))
if str(default) == 'monsterid':
monsterdata = BuildMonster(seed=kwargs['digest'], size=(size, size))
data = BytesIO()
monsterdata.save(data, 'PNG', quality=JPEG_QUALITY)
data.seek(0)
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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)
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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)
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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)
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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)
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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()
if imgformat == 'jpg':
imgformat = 'jpeg'
response = HttpResponse(
data,
content_type='image/%s' % imgformat)
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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)
response = HttpResponse(
data.read(),
content_type='image/%s' % file_format(img.format))
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
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)