Completely refactored this module. The model has been changed to reflect more sane defaults. A new set of templatetags have been created as well, however, the gravatar public API has been preserved. The only thing that still needs to be done is to rewrite the administrative views for uploading/deleting/changing avatars.

git-svn-id: http://django-avatar.googlecode.com/svn/trunk@19 c76b2324-5f53-0410-85ac-b1078a54aeeb
This commit is contained in:
Eric Florenzano 2008-08-10 22:55:25 +00:00
parent 2139fc5979
commit 810ce3f8e4
5 changed files with 149 additions and 52 deletions

View file

@ -1,14 +1,18 @@
from django.contrib.auth.models import User
from models import Avatar
from django.db.models import signals
from django.contrib.auth.models import User
from django.conf import settings
def create_avatar(sender=None, instance=None, **kwargs):
avatar, created = Avatar.objects.get_or_create(user=instance)
avatar.save()
def delete_avatar(sender=None, instance=None, **kwargs):
try:
Avatar.objects.get(user=instance).delete()
except Avatar.DoesNotExist:
pass
signals.post_save.connect(create_avatar, sender=User)
signals.post_delete.connect(delete_avatar, sender=User)
AUTO_GENERATE_AVATAR_SIZES = getattr(settings, 'AUTO_GENERATE_AVATAR_SIZES', (80,))
def update_email_hash(sender=None, instance=None, **kwargs):
for avatar in instance.avatar_set.all():
avatar.save()
signals.post_save.connect(update_email_hash, sender=User)
def create_default_thumbnails(instance=None, created=False, **kwargs):
if created:
for size in AUTO_GENERATE_AVATAR_SIZES:
instance.create_thumbnail(size)
avatar, created = Avatar.objects.get_or_create(user=instance)
signals.post_save.connect(create_default_thumbnails, sender=Avatar)

View file

@ -1,31 +1,82 @@
import re
import datetime
import os.path
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.storage import default_storage
from django.utils.translation import ugettext as _
try:
from hashlib import md5
except ImportError:
from md5 import new as md5
try:
from PIL import ImageFile
except ImportError:
import ImageFile
try:
from PIL import Image
except ImportError:
import Image
RATING_CHOICES = (
('g', 'G'),
('pg', 'PG'),
('r', 'R'),
('x', 'X'),
)
WIDTH_HEIGHT_RE = re.compile(r'\[(\d+)x(\d+)\]')
AUTO_GENERATE_AVATAR_SIZES = getattr(settings, 'AUTO_GENERATE_AVATAR_SIZES', (80,))
AVATAR_RESIZE_METHOD = getattr(settings, 'AVATAR_RESIZE_METHOD', Image.ANTIALIAS)
def avatar_file_path(instance=None, filename=None):
return 'avatars/%s/[%sx%s]%s' % (instance.user.username,
instance.avatar.width, instance.avatar.height, filename)
class Avatar(models.Model):
email_hash = models.CharField(max_length=128, blank=True)
user = models.OneToOneField(User)
# This should have a subdirectory of each user's avatar, but first we need
# patch #5361 to land into Trunk.
avatar = models.FileField(upload_to='avatars/', default='DEFAULT')
rating = models.CharField(choices=RATING_CHOICES, max_length=2, default='g')
user = models.ForeignKey(User)
primary = models.BooleanField(default=False)
avatar = models.ImageField(upload_to=avatar_file_path, blank=True)
date_uploaded = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
return u'Gravatar for %s' % self.user
return _(u'Avatar for %s' % self.user)
def save(self):
self.email_hash = md5(self.user.email).hexdigest().lower()
super(Avatar, self).save()
if self.primary:
avatars = Avatar.objects.filter(user=self.user, primary=True)
avatars.update(primary=False)
super(Avatar, self).save()
def thumbnail_exists(self, size):
if size in AUTO_GENERATE_AVATAR_SIZES:
return True
new_path_fragment = '[%sx%s]' % (size, size)
path = WIDTH_HEIGHT_RE.sub(new_path_fragment, self.avatar.path)
return default_storage.exists(path)
def create_thumbnail(self, size):
orig = default_storage.open(self.avatar.path, 'rb').read()
p = ImageFile.Parser()
p.feed(orig)
try:
image = p.close()
except IOError:
return # What should we do here? Render a "sorry, didn't work" img?
(w, h) = image.size
if w > h:
diff = (w - h) / 2
image = image.crop((diff, 0, w - diff, h))
else:
diff = (h - w) / 2
image = image.crop((0, diff, w, h - diff))
image = image.resize((size, size), AVATAR_RESIZE_METHOD)
thumb = default_storage.open(self.avatar_path(size), 'wb')
image.save(thumb, "JPEG")
def avatar_url(self, size):
new_url_fragment = '[%sx%s]' % (size, size)
return WIDTH_HEIGHT_RE.sub(new_url_fragment, self.avatar.url)
def avatar_path(self, size):
new_path_fragment = '[%sx%s]' % (size, size)
return WIDTH_HEIGHT_RE.sub(new_path_fragment, self.avatar.path)

View file

View file

@ -0,0 +1,55 @@
import os.path
from django import template
from django.utils.html import escape
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.translation import ugettext as _
register = template.Library()
AVATAR_GRAVATAR_BACKUP = getattr(settings, 'AVATAR_GRAVATAR_BACKUP', True)
AVATAR_DEFAULT_URL = getattr(settings, 'AVATAR_DEFAULT_URL',
settings.MEDIA_URL + os.path.join(os.path.dirname(__file__), 'default.jpg'))
def avatar_url(user, size=80):
if not isinstance(user, User):
try:
user = User.objects.get(username=user)
except User.DoesNotExist:
return AVATAR_DEFAULT_URL
avatars = user.avatar_set.order_by('-date_uploaded')
primary = avatars.filter(primary=True)
if primary.count() > 0:
avatar = primary[0]
elif avatars.count() > 0:
avatar = avatars[0]
else:
avatar = None
if avatar is not None:
if not avatar.thumbnail_exists(size):
avatar.create_thumbnail(size)
return avatar.avatar_url(size)
else:
if AVATAR_GRAVATAR_BACKUP:
return "http://www.gravatar.com/avatar/%s/?" % (
hashlib.md5(email).hexdigest(),
urllib.urlencode({'s': str(size)}),)
else:
return AVATAR_DEFAULT_URL
register.simple_tag(avatar_url)
def avatar(user, size=80):
if not isinstance(user, User):
try:
user = User.objects.get(username=user)
alt = unicode(user)
url = avatar_url(user, size)
except User.DoesNotExist:
url = AVATAR_DEFAULT_URL
alt = _("Default Avatar")
else:
alt = unicode(user)
url = avatar_url(user, size)
return """<img src="%s" alt="%s" />""" % (url, alt)
register.simple_tag(avatar)

View file

@ -1,14 +1,6 @@
import os
import os.path
import tempfile
try:
from PIL import ImageFile
except ImportError:
import ImageFile
try:
from PIL import Image
except ImportError:
import Image
import shutil
from models import Avatar
@ -20,17 +12,25 @@ from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from django.core.cache import cache
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from PIL import ImageFile
except ImportError:
import ImageFile
try:
from PIL import Image
except ImportError:
import Image
MAX_MEGABYTES = getattr(settings, 'AVATAR_MAX_FILESIZE', 10)
MAX_WIDTH = getattr(settings, 'AVATAR_MAX_WIDTH', 512)
DEFAULT_WIDTH = getattr(settings, 'AVATAR_DEFAULT_WIDTH', 80)
AVATAR_CACHE_SECONDS = getattr(settings, 'AVATAR_CACHE_SECONDS', None)
AVATAR_RESIZE_METHOD = getattr(settings, 'AVATAR_RESIZE_METHOD', Image.ANTIALIAS)
def _get_next(request):
"""
@ -52,7 +52,7 @@ def _get_next(request):
raise Http404 # No next url was supplied in GET or POST.
return next
def img(request, email_hash, resize_method=Image.ANTIALIAS):
def img(request, email_hash):
if email_hash.endswith('.jpg'):
email_hash = email_hash[:-4]
try:
@ -63,21 +63,16 @@ def img(request, email_hash, resize_method=Image.ANTIALIAS):
size = MAX_WIDTH
rating = request.GET.get('r', 'g') # Unused, for now.
default = request.GET.get('d', '')
if AVATAR_CACHE_SECONDS:
cache_key = '%s-%s' % (email_hash, str(size))
cached = cache.get(cache_key)
if cached:
return cached
data = None
try:
avatar = Avatar.objects.get(email_hash=email_hash)
except Avatar.DoesNotExist:
avatar = Avatar.objects.filter(email_hash=email_hash).order_by('-primary', '-date_uploaded')[0]
except IndexError:
avatar = None
except Avatar.MultipleObjectsReturned:
avatar = None
try:
if avatar is not None:
data = open(avatar.get_avatar_filename(), 'r').read()
data = open(avatar.avatar.path, 'r').read()
except IOError:
pass
if not data and default:
@ -106,11 +101,9 @@ def img(request, email_hash, resize_method=Image.ANTIALIAS):
else:
diff = (height - width) / 2
image = image.crop((0, diff, width, height - diff))
image = image.resize((size, size), resize_method)
image = image.resize((size, size), AVATAR_RESIZE_METHOD)
response = HttpResponse(mimetype='image/jpeg')
image.save(response, "JPEG")
if AVATAR_CACHE_SECONDS:
cache.set(cache_key, response, AVATAR_CACHE_SECONDS)
return response
def change(request, extra_context={}, next_override=None):
@ -134,9 +127,6 @@ def change(request, extra_context={}, next_override=None):
avatar.save()
request.user.message_set.create(
message=_("Successfully updated your avatar."))
if AVATAR_CACHE_SECONDS:
for i in xrange(512):
cache.delete('%s-%s' % (avatar.email_hash, str(i)))
return HttpResponseRedirect(next_override or _get_next(request))
return render_to_response(
'avatar/change.html',
@ -159,9 +149,6 @@ def delete(request, extra_context={}, next_override=None):
avatar.save()
request.user.message_set.create(
message=_("Successfully removed your avatar."))
if AVATAR_CACHE_SECONDS:
for i in xrange(512):
cache.delete('%s-%s' % (avatar.email_hash, str(i)))
next = next_override or _get_next(request)
return HttpResponseRedirect(next)
return render_to_response(