diff --git a/avatar/conf.py b/avatar/conf.py index fd8122f..bfe51ef 100644 --- a/avatar/conf.py +++ b/avatar/conf.py @@ -11,7 +11,6 @@ class AvatarConf(AppConf): PATH_HANDLER = 'avatar.models.avatar_path_handler' GRAVATAR_BASE_URL = 'https://www.gravatar.com/avatar/' GRAVATAR_FIELD = 'email' - GRAVATAR_BACKUP = True GRAVATAR_DEFAULT = None AVATAR_GRAVATAR_FORCEDEFAULT = False DEFAULT_URL = 'avatar/img/default.jpg' @@ -27,13 +26,17 @@ class AvatarConf(AppConf): STORAGE = settings.DEFAULT_FILE_STORAGE CLEANUP_DELETED = False AUTO_GENERATE_SIZES = (DEFAULT_SIZE,) - FACEBOOK_BACKUP = False FACEBOOK_GET_ID = None CACHE_ENABLED = True RANDOMIZE_HASHES = False ADD_TEMPLATE = '' CHANGE_TEMPLATE = '' DELETE_TEMPLATE = '' + PROVIDERS = ( + 'avatar.providers.PrimaryAvatarProvider', + 'avatar.providers.GravatarAvatarProvider', + 'avatar.providers.DefaultAvatarProvider', + ) def configure_auto_generate_avatar_sizes(self, value): return value or getattr(settings, 'AVATAR_AUTO_GENERATE_SIZES', diff --git a/avatar/providers.py b/avatar/providers.py new file mode 100644 index 0000000..7b4c5bb --- /dev/null +++ b/avatar/providers.py @@ -0,0 +1,84 @@ +import hashlib + +try: + from urllib.parse import urljoin, urlencode +except ImportError: + from urlparse import urljoin + from urllib import urlencode + + +from avatar.conf import settings +from avatar.utils import ( + force_bytes, + get_default_avatar_url, + get_primary_avatar, +) + +from django.utils.module_loading import import_string + +# If the FacebookAvatarProvider is used, a mechanism needs to be defined on +# how to obtain the user's Facebook UID. This is done via +# ``AVATAR_FACEBOOK_GET_ID``. +get_facebook_id = None + +if 'avatar.providers.FacebookAvatarProvider' in settings.AVATAR_PROVIDERS: + if callable(settings.AVATAR_FACEBOOK_GET_ID): + get_facebook_id = settings.AVATAR_FACEBOOK_GET_ID + else: + get_facebook_id = import_string(settings.AVATAR_FACEBOOK_GET_ID) + + +class DefaultAvatarProvider(object): + """ + Returns the default url defined by ``settings.DEFAULT_AVATAR_URL``. + """ + + @classmethod + def get_avatar_url(self, user, size): + return get_default_avatar_url() + + +class PrimaryAvatarProvider(object): + """ + Returns the primary Avatar from the users avatar set. + """ + + @classmethod + def get_avatar_url(self, user, size): + avatar = get_primary_avatar(user, size) + if avatar: + return avatar.avatar_url(size) + + +class GravatarAvatarProvider(object): + """ + Returns the url for an avatar by the Gravatar service. + """ + + @classmethod + def get_avatar_url(self, user, size): + params = {'s': str(size)} + if settings.AVATAR_GRAVATAR_DEFAULT: + params['d'] = settings.AVATAR_GRAVATAR_DEFAULT + if settings.AVATAR_GRAVATAR_FORCEDEFAULT: + params['f'] = 'y' + path = "%s/?%s" % (hashlib.md5(force_bytes(getattr(user, + settings.AVATAR_GRAVATAR_FIELD))).hexdigest(), urlencode(params)) + + return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path) + + +class FacebookAvatarProvider(object): + """ + Returns the url of a Facebook profile image. + """ + + @classmethod + def get_avatar_url(self, user, size): + fb_id = get_facebook_id(user) + if fb_id: + url = 'https://graph.facebook.com/{fb_id}/picture?type=square&width={size}&height={size}' + return url.format( + fb_id=fb_id, + size=size + ) diff --git a/avatar/templatetags/avatar_tags.py b/avatar/templatetags/avatar_tags.py index 29cc256..1066b96 100644 --- a/avatar/templatetags/avatar_tags.py +++ b/avatar/templatetags/avatar_tags.py @@ -1,11 +1,3 @@ -import hashlib - -try: - from urllib.parse import urljoin, urlencode -except ImportError: - from urlparse import urljoin - from urllib import urlencode - from django import template from django.core.urlresolvers import reverse from django.template.loader import render_to_string @@ -14,46 +6,26 @@ from django.utils.translation import ugettext as _ from django.utils.module_loading import import_string from avatar.conf import settings -from avatar.utils import (get_primary_avatar, get_default_avatar_url, - cache_result, get_user_model, get_user, force_bytes) from avatar.models import Avatar +from avatar.utils import ( + cache_result, + get_default_avatar_url, + get_user_model, + get_user, +) + register = template.Library() -get_facebook_id = None - -if settings.AVATAR_FACEBOOK_BACKUP: - if callable(settings.AVATAR_FACEBOOK_GET_ID): - get_facebook_id = settings.AVATAR_FACEBOOK_GET_ID - else: - get_facebook_id = import_string(settings.AVATAR_FACEBOOK_GET_ID) - @cache_result() @register.simple_tag def avatar_url(user, size=settings.AVATAR_DEFAULT_SIZE): - avatar = get_primary_avatar(user, size=size) - if avatar: - return avatar.avatar_url(size) - - if settings.AVATAR_GRAVATAR_BACKUP: - params = {'s': str(size)} - if settings.AVATAR_GRAVATAR_DEFAULT: - params['d'] = settings.AVATAR_GRAVATAR_DEFAULT - if settings.AVATAR_GRAVATAR_FORCEDEFAULT: - params['f'] = 'y' - path = "%s/?%s" % (hashlib.md5(force_bytes(getattr(user, - settings.AVATAR_GRAVATAR_FIELD))).hexdigest(), urlencode(params)) - return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path) - - if settings.AVATAR_FACEBOOK_BACKUP: - fb_id = get_facebook_id(user) - if fb_id: - return 'https://graph.facebook.com/{fb_id}/picture?type=square&width={size}&height={size}'.format( - fb_id=fb_id, size=size - ) - - return get_default_avatar_url() + for provider_path in settings.AVATAR_PROVIDERS: + provider = import_string(provider_path) + avatar_url = provider.get_avatar_url(user, size) + if avatar_url: + return avatar_url @cache_result() diff --git a/docs/index.txt b/docs/index.txt index 425fd7b..c054913 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -2,9 +2,10 @@ django-avatar ============= Django-avatar is a reusable application for handling user avatars. It has the -ability to default to Gravatar_ if no avatar is found for a certain user. -Django-avatar automatically generates thumbnails and stores them to your default -file storage backend for retrieval later. +ability to default to avatars provided by third party services (like Gravatar_ +or Facebook) if no avatar is found for a certain user. Django-avatar +automatically generates thumbnails and stores them to your default file +storage backend for retrieval later. .. _Gravatar: http://gravatar.com @@ -103,89 +104,131 @@ Global Settings There are a number of settings available to easily customize the avatars that appear on the site. Listed below are those settings: -AVATAR_AUTO_GENERATE_SIZES +.. py:data:: AVATAR_AUTO_GENERATE_SIZES + An iterable of integers representing the sizes of avatars to generate on upload. This can save rendering time later on if you pre-generate the resized versions. Defaults to ``(80,)`` -AVATAR_CACHE_ENABLED +.. py:data:: AVATAR_CACHE_ENABLED + Set to ``False`` if you completely disable avatar caching. Defaults to ``True``. -AVATAR_DEFAULT_URL - The default URL to default to if ``AVATAR_GRAVATAR_BACKUP`` is set to False - and there is no ``Avatar`` instance found in the system for the given user. +.. py:data:: AVATAR_DEFAULT_URL -AVATAR_EXPOSE_USERNAMES - Puts the User's username field in the URL path when ``True``. Set to ``False`` to - use the User's primary key instead, preventing their email from being searchable on the web. - Defaults to ``True``. + The default URL to default to if the + :py:class:`~avatar.providers.GravatarAvatarProvider` is not used and there + is no ``Avatar`` instance found in the system for the given user. -AVATAR_FACEBOOK_BACKUP - A bool determining whether to default to Facebook Graph service if no ``Avatar`` instance - is found in the system for the given user. ``AVATAR_GRAVATAR_BACKUP`` takes precedence, so - if you set this to ``True`` then you must set ``AVATAR_GRAVATAR_BACKUP`` to False. You - must also set the ``AVATAR_FACEBOOK_GET_ID`` setting. - Defaults to ``False``. +.. py:data:: AVATAR_EXPOSE_USERNAMES -AVATAR_FACEBOOK_GET_ID - A callable or string path to a callable that will return the user's Facebook ID. The - callable should take a ``User`` object and return a string. If you want to use this - then make sure ``AVATAR_FACEBOOK_BACKUP`` is ``True`` and ``AVATAR_GRAVATAR_BACKUP`` is - ``False``. Defaults to ``None``. + Puts the User's username field in the URL path when ``True``. Set to + ``False`` to use the User's primary key instead, preventing their email + from being searchable on the web. Defaults to ``True``. -AVATAR_GRAVATAR_BACKUP - A bool determining whether to default to the Gravatar service if no - ``Avatar`` instance is found in the system for the given user. Defaults to - ``True``. +.. py:data:: AVATAR_FACEBOOK_GET_ID -AVATAR_GRAVATAR_DEFAULT - A string determining the default Gravatar. Can be a URL to a custom image or a - style of Gravatar. Ex. `retro`. All Available options listed in the - `Gravatar documentation `_. - Defaults to ``None``. + A callable or string path to a callable that will return the user's + Facebook ID. The callable should take a ``User`` object and return a + string. If you want to use this then make sure you included + :py:class:`~avatar.providers.FacebookAvatarProvider` in :py:data:`AVATAR_PROVIDERS`. -AVATAR_GRAVATAR_FORCEDEFAULT - A bool indicating whether or not to always use the default Gravitar. More details can be found - in the `Gravatar documentation `_. - Defaults to ``False``. +.. py:data:: AVATAR_GRAVATAR_DEFAULT -AVATAR_GRAVATAR_FIELD - The name of the user's field containing the gravatar email. For example, if you set - this to ``gravatar`` then django-avatar will get the user's gravatar in ``user.gravatar``. - Defaults to ``email``. + A string determining the default Gravatar. Can be a URL to a custom image + or a style of Gravatar. Ex. `retro`. All Available options listed in the + `Gravatar documentation `_. Defaults to ``None``. + +.. py:data:: AVATAR_GRAVATAR_FORCEDEFAULT + + A bool indicating whether or not to always use the default Gravitar. More + details can be found in the `Gravatar documentation + `_. Defaults + to ``False``. + +.. py:data:: AVATAR_GRAVATAR_FIELD + + The name of the user's field containing the gravatar email. For example, + if you set this to ``gravatar`` then django-avatar will get the user's + gravatar in ``user.gravatar``. Defaults to ``email``. + +.. py:data:: AVATAR_MAX_SIZE -AVATAR_MAX_SIZE File size limit for avatar upload. Default is ``1024 * 1024`` (1 MB). -AVATAR_PATH_HANDLER +.. py:data:: AVATAR_PATH_HANDLER + Path to a method for avatar file path handling. Default is ``avatar.models.avatar_path_handler``. -AVATAR_RESIZE_METHOD +.. py:data:: AVATAR_PROVIDERS + + Tuple of classes that are tried in the given order for returning avatar + URLs. + Defaults to:: + + ( + 'avatar.providers.PrimaryAvatarProvider', + 'avatar.providers.GravatarAvatarProvider', + 'avatar.providers.DefaultAvatarProvider', + ) + + If you want to implement your own provider, it must provide a class method + ``get_avatar_url(user, size)``. + + .. py:class:: avatar.providers.GravatarAvatarProvider + + Returns the primary avatar stored for the given user. + + .. py:class:: avatar.providers.GravatarAvatarProvider + + Adds support for the Gravatar service and will always return an avatar + URL. If the user has no avatar registered with Gravatar a default will + be used (see :py:data:`AVATAR_GRAVATAR_DEFAULT`). + + .. py:class:: avatar.providers.FacebookAvatarProvider + + Add this provider to :py:data:`AVATAR_PROVIDERS` in order to add + support for profile images from Facebook. Note that you also need to + set the :py:data:`AVATAR_FACEBOOK_GET_ID` setting. + + .. py:class:: avatar.providers.DefaultAvatarProvider + + Provides a fallback avatar defined in :py:data:`AVATAR_DEFAULT_URL`. + +.. py:data:: AVATAR_RESIZE_METHOD + The method to use when resizing images, based on the options available in PIL. Defaults to ``Image.ANTIALIAS``. -AVATAR_STORAGE_DIR +.. py:data:: AVATAR_STORAGE_DIR + The directory under ``MEDIA_ROOT`` to store the images. If using a non-filesystem storage device, this will simply be appended to the beginning of the file name. Defaults to ``avatars``. -AVATAR_ADD_TEMPLATE - Path to the Django template to use for adding a new avatar. Defaults to ``avatar/add.html``. +.. py:data:: AVATAR_ADD_TEMPLATE + + Path to the Django template to use for adding a new avatar. Defaults to + ``avatar/add.html``. + +.. py:data:: AVATAR_CHANGE_TEMPLATE -AVATAR_CHANGE_TEMPLATE Path to the Django template to use for changing a user's avatar. Defaults to ``avatar/change.html``. -AVATAR_DELETE_TEMPLATE - Path to the Django template to use for confirming a delete of a user's avatar. Defaults to ``avatar/avatar/confirm_delete.html``. +.. py:data:: AVATAR_DELETE_TEMPLATE + + Path to the Django template to use for confirming a delete of a user's + avatar. Defaults to ``avatar/avatar/confirm_delete.html``. Management Commands ------------------- This application does include one management command: ``rebuild_avatars``. It takes no arguments and, when run, re-renders all of the thumbnails for all of -the avatars for the pixel sizes specified in the ``AVATAR_AUTO_GENERATE_SIZES`` -setting. +the avatars for the pixel sizes specified in the +:py:data:`AVATAR_AUTO_GENERATE_SIZES` setting. .. _pip: http://www.pip-installer.org/