mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-14 20:18:02 +00:00
330 lines
13 KiB
Python
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)
|