Commit 89e94602 authored by Michal Čihař's avatar Michal Čihař

Infrastructure for caching avatars

Avatars now can be fetched server side and are cached on the server.

Issue #412
Signed-off-by: default avatarMichal Čihař <michal@cihar.com>
parent 440425a0
# -*- coding: utf-8 -*-
#
# Copyright © 2012 - 2014 Michal Čihař <michal@cihar.com>
#
# This file is part of Weblate <http://weblate.org/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import urllib2
import urllib
import hashlib
import os.path
from django.core.cache import get_cache, InvalidCacheBackendError
from django.conf import settings
import weblate
from weblate import appsettings
try:
import libravatar
HAS_LIBRAVATAR = True
except ImportError:
HAS_LIBRAVATAR = False
AVATAR_URL_PREFIX = getattr(
settings,
'AVATAR_URL_PREFIX',
'https://seccdn.libravatar.org/'
)
# See http://wiki.libravatar.org/api/
# for available choices
AVATAR_DEFAULT_IMAGE = getattr(
settings,
'AVATAR_DEFAULT_IMAGE',
'identicon'
)
PLURAL_SEPARATOR = '\x1e\x1e'
def avatar_for_email(email, size=80):
'''
Generates url for avatar.
'''
# Safely handle blank email
if email == '':
email = 'noreply@weblate.org'
# Retrieve from cache
cache_key = 'avatar-%s-%s' % (email, size)
cache = get_cache('default')
url = cache.get(cache_key)
if url is not None:
return url
if HAS_LIBRAVATAR:
# Use libravatar library if available
url = libravatar.libravatar_url(
email=email,
https=True,
default=AVATAR_DEFAULT_IMAGE,
size=size
)
else:
# Fallback to standard method
mail_hash = hashlib.md5(email.lower()).hexdigest()
url = "%savatar/%s?" % (AVATAR_URL_PREFIX, mail_hash)
url += urllib.urlencode({
's': str(size),
'd': AVATAR_DEFAULT_IMAGE
})
# Store result in cache
cache.set(cache_key, url, 3600 * 24)
return url
def get_avatar_image(user, size):
'''
Returns avatar image from cache (if available) or downloads it.
'''
cache_key = 'avatar-img-{0}-{1}'.format(
user.username,
size
)
# Try using avatar specific cache if available
try:
cache = get_cache('avatar')
except InvalidCacheBackendError:
cache = get_cache('default')
image = cache.get(cache_key)
if image is None:
try:
image = download_avatar_image(user, size)
cache.set(cache_key, image)
except IOError as error:
weblate.logger.error(
'Failed to fetch avatar for %s: %s',
user.username,
str(error)
)
fallback = os.path.join(
appsettings.WEB_ROOT,
'media/weblate-{0}.png'.format(size)
)
with open(fallback, 'r') as handle:
return handle.read()
return image
def download_avatar_image(user, size):
'''
Downloads avatar image from remote server.
'''
url = avatar_for_email(user.email, size)
request = urllib2.Request(url)
request.timeout = 0.5
request.add_header('User-Agent', 'Weblate/%s' % weblate.VERSION)
# Fire request
handle = urllib2.urlopen(request)
# Read and possibly convert response
return handle.read()
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
# #
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.cache import cache_page
from django.http import HttpResponse
from django.contrib.auth import logout from django.contrib.auth import logout
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
...@@ -40,6 +42,7 @@ from social.apps.django_app.utils import BACKENDS ...@@ -40,6 +42,7 @@ from social.apps.django_app.utils import BACKENDS
from social.apps.django_app.views import complete from social.apps.django_app.views import complete
import weblate import weblate
from weblate.accounts.avatar import get_avatar_image
from weblate.accounts.models import set_lang, remove_user, Profile from weblate.accounts.models import set_lang, remove_user, Profile
from weblate.trans.models import Change, Project from weblate.trans.models import Change, Project
from weblate.accounts.forms import ( from weblate.accounts.forms import (
...@@ -331,6 +334,19 @@ def user_page(request, user): ...@@ -331,6 +334,19 @@ def user_page(request, user):
) )
@cache_page(3600 * 24)
def user_avatar(request, user, size):
'''
User avatar page.
'''
user = get_object_or_404(User, username=user)
return HttpResponse(
content_type='image/png',
content=get_avatar_image(user, size)
)
def weblate_login(request): def weblate_login(request):
''' '''
Login handler, just wrapper around login. Login handler, just wrapper around login.
......
...@@ -631,11 +631,18 @@ urlpatterns = patterns( ...@@ -631,11 +631,18 @@ urlpatterns = patterns(
# User pages # User pages
url( url(
r'^user/(?P<user>[^/]+)/', r'^user/(?P<user>[^/]+)/$',
'weblate.accounts.views.user_page', 'weblate.accounts.views.user_page',
name='user_page', name='user_page',
), ),
# Avatars
url(
r'^user/(?P<user>[^/]+)/avatar/(?P<size>(32|128))/$',
'weblate.accounts.views.user_avatar',
name='user_avatar',
),
# Sitemap # Sitemap
url( url(
r'^sitemap\.xml$', r'^sitemap\.xml$',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment