Permalink
Browse files

Initial version.

git-svn-id: http://django-avatar.googlecode.com/svn/trunk@2 c76b2324-5f53-0410-85ac-b1078a54aeeb
  • Loading branch information...
1 parent 2b22850 commit 9e85aba9f75af15b557ec9ba0e2bff5d57b71be0 @ericflo ericflo committed Aug 1, 2008
View
@@ -0,0 +1 @@
+
View
@@ -0,0 +1 @@
+
View
@@ -0,0 +1,29 @@
+Copyright (c) 2008, Eric Florenzano
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of the author nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
View
@@ -0,0 +1 @@
+
View
@@ -0,0 +1,15 @@
+from django.contrib.auth.models import User
+from models import Avatar
+from django.dispatch import dispatcher
+from django.db.models import signals
+
+def create_avatar(sender, instance):
+ avatar, created = Avatar.objects.get_or_create(user=instance)
+ avatar.save()
+def delete_avatar(sender, instance):
+ try:
+ Avatar.objects.get(user=instance).delete()
+ except Avatar.DoesNotExist:
+ pass
+dispatcher.connect(create_avatar, sender=User, signal=signals.post_save)
+dispatcher.connect(delete_avatar, sender=User, signal=signals.post_delete)
View
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from models import Avatar
+
+admin.site.register(Avatar)
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1 @@
+
@@ -0,0 +1 @@
+
@@ -0,0 +1,51 @@
+import os
+import os.path
+
+from urllib2 import urlopen
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.core.management.base import NoArgsCommand
+
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import new as md5
+
+def hashify(inp):
+ return md5(inp).hexdigest().lower()
+
+class Command(NoArgsCommand):
+ help = "Import avatars from Gravatar, and store them locally."
+
+ def handle_noargs(self, **options):
+ # I'm OK with bare exceptions here, since we want totally graceful and
+ # aggressive failure on a massive import like this.
+ for user in User.objects.all():
+ if user.email:
+ url = "http://www.gravatar.com/avatar/%s" % hashify(user.email)
+ try:
+ data = urlopen(url).read()
+ except:
+ print "Errored on opening URL: %s" % url
+ continue
+ else:
+ print "%s has no e-mail address specified." % user.username
+ continue
+ dirname = os.path.join(settings.MEDIA_ROOT, 'avatars')
+ try:
+ os.makedirs(dirname)
+ except OSError:
+ pass
+ filename = "%s.jpg" % user.avatar.email_hash
+ full_filename = os.path.join(dirname, filename)
+ try:
+ f = open(full_filename, 'w')
+ f.write(data)
+ f.close()
+ user.avatar.avatar = full_filename
+ user.avatar.save()
+ print "Imported Gravatar for %s" % user.username
+ except:
+ print "Error on writing to file: %s" % full_filename
+ continue
+
View
@@ -0,0 +1,31 @@
+import os.path
+
+from django.db import models
+from django.contrib.auth.models import User
+
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import new as md5
+
+RATING_CHOICES = (
+ ('g', 'G'),
+ ('pg', 'PG'),
+ ('r', 'R'),
+ ('x', 'X'),
+)
+
+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')
+
+ def __unicode__(self):
+ return u'Gravatar for %s' % self.user
+
+ def save(self):
+ self.email_hash = md5(self.user.email).hexdigest().lower()
+ super(Avatar, self).save()
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>{% block title %}django-avatar{% endblock %}</title>
+ </head>
+ <body>
+ {% block content %}{% endblock %}
+ </body>
+</html>
@@ -0,0 +1,10 @@
+{% extends "avatar/base.html" %}
+
+{% block content %}
+ <p>Your current avatar looks like this:</p>
+ <img src="{% url avatar_img avatar.email_hash %}" />
+ <form enctype="multipart/form-data" method="POST" action="">
+ <input type="file" name="avatar" value="Avatar Image" />
+ <input type="submit" value="Upload New Image" />
+ </form>
+{% endblock %}
@@ -0,0 +1,10 @@
+{% extends "avatar/base.html" %}
+
+{% block content %}
+ <p>Are you sure that you would like to delete the avatar of {{ avatar.user.username }}?</p>
+ <form method="POST" action="">
+ <input type="hidden" name="next" value="{{ next }}">
+ <input type="submit" value="Yes" />
+ </form>
+ <a href="{{ next }}">No</a>
+{% endblock %}
View
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('avatar',
+ url('^change/$', 'views.change', name='avatar_change'),
+ url('^delete/$', 'views.delete', name='avatar_delete'),
+ url('^(\w+)/$', 'views.img', name='avatar_img'),
+)
View
@@ -0,0 +1,140 @@
+import os
+import os.path
+import tempfile
+import ImageFile
+import Image
+import shutil
+
+from models import Avatar
+from urllib2 import urlopen
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.conf import settings
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+MAX_MEGABYTES = getattr(settings, 'AVATAR_MAX_FILESIZE', 10)
+MAX_WIDTH = getattr(settings, 'AVATAR_MAX_WIDTH', 512)
+DEFAULT_WIDTH = getattr(settings, 'AVATAR_DEFAULT_WIDTH', 80)
+
+def _get_next(request):
+ """
+ The part that's the least straightforward about views in this module is how they
+ determine their redirects after they have finished computation.
+
+ In short, they will try and determine the next place to go in the following order:
+
+ 1. If there is a variable named ``next`` in the *POST* parameters, the view will
+ redirect to that variable's value.
+ 2. If there is a variable named ``next`` in the *GET* parameters, the view will
+ redirect to that variable's value.
+ 3. If Django can determine the previous page from the HTTP headers, the view will
+ redirect to that previous page.
+ 4. Otherwise, the view raise a 404 Not Found.
+ """
+ next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', None)))
+ if not next or next == request.path:
+ raise Http404 # No next url was supplied in GET or POST.
+ return next
+
+def img(request, email_hash, resize_method=Image.ANTIALIAS):
+ if email_hash.endswith('.jpg'):
+ email_hash = email_hash[:-4]
+ try:
+ size = int(request.GET.get('s', DEFAULT_WIDTH))
+ except ValueError:
+ size = DEFAULT_WIDTH
+ if size > MAX_WIDTH:
+ size = MAX_WIDTH
+ rating = request.GET.get('r', 'g') # Unused, for now.
+ default = request.GET.get('d', '')
+ data = None
+ try:
+ avatar = Avatar.objects.get(email_hash=email_hash)
+ try:
+ data = open(avatar.get_avatar_filename(), 'r').read()
+ except IOError:
+ pass
+ except Avatar.DoesNotExist:
+ if default:
+ try:
+ data = urlopen(default).read()
+ except: #TODO: Fix this hardcore
+ pass
+ if not data:
+ filename = os.path.join(os.path.dirname(__file__), 'default.jpg')
+ data = open(filename, 'r').read()
+ p = ImageFile.Parser()
+ p.feed(data)
+ try:
+ image = p.close()
+ except IOError:
+ filename = os.path.join(os.path.dirname(__file__), 'default.jpg')
+ try:
+ return HttpResponse(open(filename, 'r').read(), mimetype='image/jpeg')
+ except: #TODO: Fix this hardcore
+ # Is this the right response after so many other things have failed?
+ raise Http404
+ (width, height) = image.size
+ if width > height:
+ diff = (width - height) / 2
+ image = image.crop((diff, 0, width - diff, height))
+ else:
+ diff = (height - width) / 2
+ image = image.crop((0, diff, width, height - diff))
+ image = image.resize((size, size), resize_method)
+ response = HttpResponse(mimetype='image/jpeg')
+ image.save(response, "JPEG")
+ return response
+
+def change(request, extra_context={}, next_override=None):
+ if request.method == "POST":
+ dirname = os.path.join(settings.MEDIA_ROOT, 'avatars')
+ filename = "%s.jpg" % request.user.avatar.email_hash
+ full_filename = os.path.join(dirname, filename)
+ (destination, destination_path) = tempfile.mkstemp()
+ for i, chunk in enumerate(request.FILES['avatar'].chunks()):
+ if i * 16 == MAX_MEGABYTES:
+ raise Http404
+ os.write(destination, chunk)
+ os.close(destination)
+ shutil.move(destination_path, full_filename)
+ request.user.avatar.avatar = full_filename
+ request.user.avatar.save()
+ return HttpResponseRedirect(next_override or _get_next(request))
+ return render_to_response(
+ 'avatar/change.html',
+ extra_context,
+ context_instance = RequestContext(
+ request,
+ { 'avatar': request.user.avatar,
+ 'next': next_override or _get_next(request) }
+ )
+ )
+change = login_required(change)
+
+def delete(request, extra_context={}, next_override=None):
+ if request.method == 'POST':
+ # Should we really delete a OneToOneField?
+ # I think just set image to default.
+ # request.user.avatar.delete()
+ request.user.avatar.avatar = "DEFAULT"
+ request.user.avatar.save()
+ next = next_override or _get_next(request)
+ return HttpResponseRedirect(next)
+ return render_to_response(
+ 'avatar/confirm_delete.html',
+ extra_context,
+ context_instance = RequestContext(
+ request,
+ { 'avatar': request.user.avatar,
+ 'next': next_override or _get_next(request) }
+ )
+ )
+delete = login_required(delete)

0 comments on commit 9e85aba

Please sign in to comment.