If you perform a quick Google search for grabbing Gravatar profile photos for your users in Django, you may find a handful of snippets of code showing you how to build a template tag for doing so. This is great, because Gravatar is meant to be dynamic, and you want to be able to write that directly into your template so that you always have the latest Gravatar photo. However, there can be times where you simply want to "attach" a user's Gravatar photo to the user model itself, and have that information remain as part of the user model (which "feels" right to me, since having a dynamic Gravatar photo exist only in the template essentially means my user model doesn't "know" about its own photo, and that kind of irks me).
Obviously, you can remove the if not self.photo line if you want to check for the Gravatar photo upon every invocation of save()... just keep in mind that you'll be making an extra HTTP request (and possibly timeout) every single time.
Basically, we start by building the URL, which finally ends up in gravatar_query. Then, we go ahead and start making our HTTP requests. Notice that I wrapped the whole request portion in a try block. The reason for this is that I don't want the user save() method throwing an error because of a problem with Gravatar's site (otherwise we become dependent on their site for our code to work properly). So if the request throws an error, we'll just go ahead and use the default photo. Additional note: you could even change the except line to except:, so it would just grab any error, since there are other things that could mess up here (such as IOError), and you really don't want to bomb out on saving your user.
Once the request is completed to Gravatar, we check to see what type of file was returned. Yes, I know, I'm nice enough to check for TIFFs here (hey, it can happen). We write the contents to a temporary file, and once that file is completed, we go ahead and save it into the model with a new filename (in this case, I'm using a SlugField, not only to avoid file naming conflicts but also to be able to easily match up a photo with a user... you can, of course, do whatever you want here).
Finish it off by calling the parent's save() method.
And that's it! Until next time.
The Solution
The solution here is to override your user model's save() method. We can build the code to query Gravatar directly into this method, and call it when your user is saved without a profile photo.
For the code I'll be using, I'm assuming your user model has the following fields: email, photo, slug. The "slug" field is optional, since I'm only using it below to name the image file that we download from Gravatar.
Your field definitions might look something like this:
email = models.EmailField(max_length=254, unique=True, db_index=True) photo = models.ImageField(upload_to='users/photos', blank=True) slug = models.SlugField(unique=True, blank=True)
Overriding save()
So let's override the save() method for your user. Here is the code:
def save(self, *args, **kwargs):
"""
Overrides CustomUser's save() method. Assumes that CustomUser has
self.photo, self.email, and self.slug fields. Adjust code as
necessary for your model.
"""
if not self.photo:
# try Gravatar (or use the default photo)
default_photo = 'users/photos/generic_profile_photo.png'
default_photo_url = settings.MEDIA_URL + default_photo
gravatar_base = 'http://www.gravatar.com/avatar/'
gravatar_email = md5(self.email.lower()).hexdigest()
gravatar_default = urlencode({'d': default_photo_url})
gravatar_query = '{0}{1}?{2}'.format(gravatar_base,
gravatar_email,
gravatar_default)
try:
tmp_photo = NamedTemporaryFile(delete=True)
retr_photo = urlopen(gravatar_query)
content_type = retr_photo.headers.getheader('Content-Type',
None)
file_type = None
if content_type:
if content_type == 'image/jpeg':
file_type = 'jpg'
elif content_type == 'image/png':
file_type = 'png'
elif content_type == 'image/bmp':
file_type = 'bmp'
elif content_type == 'image/gif':
file_type = 'gif'
elif content_type == 'image/tiff':
file_type = 'tiff'
tmp_photo.write(retr_photo.read())
tmp_photo.flush()
self.photo.save('users/photos/{0}.{1}'.format(self.slug,
file_type if file_type else 'png'),
File(tmp_photo), save=True)
except HTTPError:
self.photo = default_photo
super(DJ, self).save(*args, **kwargs)
Obviously, you can remove the if not self.photo line if you want to check for the Gravatar photo upon every invocation of save()... just keep in mind that you'll be making an extra HTTP request (and possibly timeout) every single time.
Basically, we start by building the URL, which finally ends up in gravatar_query. Then, we go ahead and start making our HTTP requests. Notice that I wrapped the whole request portion in a try block. The reason for this is that I don't want the user save() method throwing an error because of a problem with Gravatar's site (otherwise we become dependent on their site for our code to work properly). So if the request throws an error, we'll just go ahead and use the default photo. Additional note: you could even change the except line to except:, so it would just grab any error, since there are other things that could mess up here (such as IOError), and you really don't want to bomb out on saving your user.
Once the request is completed to Gravatar, we check to see what type of file was returned. Yes, I know, I'm nice enough to check for TIFFs here (hey, it can happen). We write the contents to a temporary file, and once that file is completed, we go ahead and save it into the model with a new filename (in this case, I'm using a SlugField, not only to avoid file naming conflicts but also to be able to easily match up a photo with a user... you can, of course, do whatever you want here).
Finish it off by calling the parent's save() method.
And that's it! Until next time.
0 comments:
Post a Comment