mirror of
https://github.com/jazzband/django-avatar.git
synced 2026-05-28 16:58:19 +00:00
Merge 4d8de28e65 into 206682ce0a
This commit is contained in:
commit
d68749623e
28 changed files with 977 additions and 62 deletions
20
README.rst
20
README.rst
|
|
@ -1,13 +1,21 @@
|
|||
=============
|
||||
django-avatar
|
||||
=============
|
||||
==============
|
||||
django-avatar2
|
||||
==============
|
||||
|
||||
.. image:: https://secure.travis-ci.org/jezdez/django-avatar.png
|
||||
:target: http://travis-ci.org/jezdez/django-avatar
|
||||
This is a fork of original https://github.com/jezdez/django-avatar . This one
|
||||
is more updated as I merged more than 10 long-standing pull requests from the
|
||||
original repository.
|
||||
|
||||
.. image:: https://secure.travis-ci.org/tbabej/django-avatar.png
|
||||
:target: http://travis-ci.org/tbabej/django-avatar
|
||||
|
||||
(travis builds refers to this fork)
|
||||
|
||||
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.
|
||||
|
||||
For more information see the documentation at http://django-avatar.readthedocs.org/
|
||||
For more information see the documentation at http://django-avatar2.readthedocs.org/
|
||||
|
||||
(documentation refers to this fork)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import six
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from avatar.models import Avatar
|
||||
from avatar.signals import avatar_updated
|
||||
from avatar.templatetags.avatar_tags import avatar
|
||||
from avatar.util import get_user_model
|
||||
|
||||
|
||||
|
|
@ -14,7 +15,13 @@ class AvatarAdmin(admin.ModelAdmin):
|
|||
list_per_page = 50
|
||||
|
||||
def get_avatar(self, avatar_in):
|
||||
return avatar(avatar_in.user, 80)
|
||||
context = dict({
|
||||
'user': avatar_in.user,
|
||||
'url': avatar_in.avatar.url,
|
||||
'alt': six.text_type(avatar_in.user),
|
||||
'size': 80,
|
||||
})
|
||||
return render_to_string('avatar/avatar_tag.html', context)
|
||||
|
||||
get_avatar.short_description = _('Avatar')
|
||||
get_avatar.allow_tags = True
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ class AvatarConf(AppConf):
|
|||
DEFAULT_SIZE = 80
|
||||
RESIZE_METHOD = Image.ANTIALIAS
|
||||
STORAGE_DIR = 'avatars'
|
||||
STORAGE_PARAMS = {}
|
||||
GRAVATAR_FIELD = 'email'
|
||||
GRAVATAR_BASE_URL = 'http://www.gravatar.com/avatar/'
|
||||
GRAVATAR_BACKUP = True
|
||||
GRAVATAR_DEFAULT = None
|
||||
|
|
@ -16,6 +18,7 @@ class AvatarConf(AppConf):
|
|||
MAX_SIZE = 1024 * 1024
|
||||
THUMB_FORMAT = 'JPEG'
|
||||
THUMB_QUALITY = 85
|
||||
USERID_AS_USERDIRNAME = False
|
||||
HASH_FILENAMES = False
|
||||
HASH_USERDIRNAMES = False
|
||||
ALLOWED_FILE_EXTS = None
|
||||
|
|
@ -23,7 +26,8 @@ class AvatarConf(AppConf):
|
|||
STORAGE = settings.DEFAULT_FILE_STORAGE
|
||||
CLEANUP_DELETED = False
|
||||
AUTO_GENERATE_SIZES = (DEFAULT_SIZE,)
|
||||
AVATAR_ALLOWED_MIMETYPES = []
|
||||
|
||||
def configure_auto_generate_avatar_sizes(self, value):
|
||||
return value or getattr(settings, 'AUTO_GENERATE_AVATAR_SIZES',
|
||||
return value or getattr(settings, 'AVATAR_AUTO_GENERATE_SIZES',
|
||||
(self.DEFAULT_SIZE,))
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def avatar_img(avatar, size):
|
|||
|
||||
class UploadAvatarForm(forms.Form):
|
||||
|
||||
avatar = forms.ImageField(label=_("avatar"))
|
||||
avatar = forms.ImageField(label=_("Avatar"))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.user = kwargs.pop('user')
|
||||
|
|
@ -30,6 +30,38 @@ class UploadAvatarForm(forms.Form):
|
|||
def clean_avatar(self):
|
||||
data = self.cleaned_data['avatar']
|
||||
|
||||
if settings.AVATAR_ALLOWED_MIMETYPES:
|
||||
try:
|
||||
import magic
|
||||
except ImportError:
|
||||
raise ImportError("python-magic library must be installed in "
|
||||
"order to use uploaded file content "
|
||||
"limitation")
|
||||
|
||||
# Construct 256 bytes needed for mime validation
|
||||
magic_buffer = six.b('')
|
||||
for chunk in data.chunks():
|
||||
magic_buffer += chunk
|
||||
if len(magic_buffer) >= 256:
|
||||
break
|
||||
|
||||
# https://github.com/ahupp/python-magic#usage
|
||||
mime = magic.from_buffer(magic_buffer, mime=True)
|
||||
if six.PY3:
|
||||
mime = mime.decode('utf-8')
|
||||
if mime not in settings.AVATAR_ALLOWED_MIMETYPES:
|
||||
err = _(
|
||||
"File content is invalid. Detected: %(mimetype)s "
|
||||
"Allowed content types are: %(valid_mime_list)s"
|
||||
)
|
||||
|
||||
conf = {
|
||||
'valid_mime_list': ", ".join(settings.AVATAR_ALLOWED_MIMETYPES),
|
||||
'mimetype': mime
|
||||
}
|
||||
|
||||
raise forms.ValidationError(err % conf)
|
||||
|
||||
if settings.AVATAR_ALLOWED_FILE_EXTS:
|
||||
root, ext = os.path.splitext(data.name.lower())
|
||||
if ext not in settings.AVATAR_ALLOWED_FILE_EXTS:
|
||||
|
|
@ -57,6 +89,7 @@ class UploadAvatarForm(forms.Form):
|
|||
'nb_avatars': count,
|
||||
'nb_max_avatars': settings.AVATAR_MAX_AVATARS_PER_USER,
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
|
@ -68,7 +101,7 @@ class PrimaryAvatarForm(forms.Form):
|
|||
avatars = kwargs.pop('avatars')
|
||||
super(PrimaryAvatarForm, self).__init__(*args, **kwargs)
|
||||
choices = [(avatar.id, avatar_img(avatar, size)) for avatar in avatars]
|
||||
self.fields['choice'] = forms.ChoiceField(label=_("Choices"),
|
||||
self.fields['choice'] = forms.ChoiceField(label=_("Available avatars:"),
|
||||
choices=choices,
|
||||
widget=widgets.RadioSelect)
|
||||
|
||||
|
|
@ -81,6 +114,6 @@ class DeleteAvatarForm(forms.Form):
|
|||
avatars = kwargs.pop('avatars')
|
||||
super(DeleteAvatarForm, self).__init__(*args, **kwargs)
|
||||
choices = [(avatar.id, avatar_img(avatar, size)) for avatar in avatars]
|
||||
self.fields['choices'] = forms.MultipleChoiceField(label=_("Choices"),
|
||||
self.fields['choices'] = forms.MultipleChoiceField(label=_("Available avatars:"),
|
||||
choices=choices,
|
||||
widget=widgets.CheckboxSelectMultiple)
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
BIN
avatar/locale/ja/LC_MESSAGES/django.mo
Normal file
BIN
avatar/locale/ja/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
131
avatar/locale/ja/LC_MESSAGES/django.po
Normal file
131
avatar/locale/ja/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-09-18 19:49+0900\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: admin.py:19
|
||||
msgid "Avatar"
|
||||
msgstr "プロフィール画像"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "avatar"
|
||||
msgstr "プロフィール画像"
|
||||
|
||||
#: forms.py:37
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : %"
|
||||
"(valid_exts_list)s"
|
||||
msgstr "%(ext)s は利用できない拡張子です。 使用可能な拡張子 : %(valid_exts_list)s"
|
||||
|
||||
#: forms.py:44
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your file is too big (%(size)s), the maximum allowed size is %"
|
||||
"(max_valid_size)s"
|
||||
msgstr "ファイルが大きすぎます(%(size)s)。アップロード可能な最大サイズは %(max_valid_size)s です。"
|
||||
|
||||
#: forms.py:54
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is %"
|
||||
"(nb_max_avatars)d."
|
||||
msgstr "登録可能なプロフィール画像は %(nb_max_avatars)d 個までです。すでに %(nb_avatars)d 個登録されています。"
|
||||
|
||||
#: forms.py:71 forms.py:84
|
||||
msgid "Choices"
|
||||
msgstr "選択"
|
||||
|
||||
#: views.py:74
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "新しいプロフィール画像をアップロードしました。"
|
||||
|
||||
#: views.py:110
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "プロフィール画像を更新しました。"
|
||||
|
||||
#: views.py:148
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
msgstr "指定されたプロフィール画像を削除しました。"
|
||||
|
||||
#: templates/avatar/add.html:6 templates/avatar/change.html:6
|
||||
msgid "Your current avatar: "
|
||||
msgstr "現在のプロフィール画像:"
|
||||
|
||||
#: templates/avatar/add.html:9 templates/avatar/change.html:9
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr "登録されているプロフィール画像はありません。アップロードしてください。"
|
||||
|
||||
#: templates/avatar/add.html:13 templates/avatar/change.html:20
|
||||
msgid "Upload New Image"
|
||||
msgstr "新しい画像のアップロード"
|
||||
|
||||
#: templates/avatar/change.html:15
|
||||
msgid "Choose new Default"
|
||||
msgstr "デフォルトの画像を選択"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:6
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr "削除したいプロフィール画像を選択してください。"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:9
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
|
||||
"\">upload one</a> now."
|
||||
msgstr "削除できるプロフィール画像はありません。<a href=\"%(avatar_change_url)s\">新規画像のアップロード</a>."
|
||||
|
||||
#: templates/avatar/confirm_delete.html:15
|
||||
msgid "Delete These"
|
||||
msgstr "削除"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(avatar_creator)s has updated their avatar %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr "%(avatar_creator)s さんがプロフィール画像 %(avatar)s をアップロードしました。\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> has updated their avatar <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr "<a href=\"%(user_url)s\">%(avatar_creator)s</a> さんがプロフィール画像 <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a> をアップロードしました。"
|
||||
|
||||
#: templates/notification/avatar_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your avatar has been updated. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr "プロフィール画像を更新しました。 %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr "プロフィール画像を更新しました。 <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templatetags/avatar_tags.py:51
|
||||
msgid "Default Avatar"
|
||||
msgstr "デフォルトのプロフィール画像"
|
||||
BIN
avatar/locale/nl/LC_MESSAGES/django.mo
Normal file
BIN
avatar/locale/nl/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
141
avatar/locale/nl/LC_MESSAGES/django.po
Normal file
141
avatar/locale/nl/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-11-11 12:32+0100\n"
|
||||
"PO-Revision-Date: 2013-11-11 12:49+0100\n"
|
||||
"Last-Translator: Ivor <ivorbosloper@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.5.5\n"
|
||||
|
||||
#: admin.py:19
|
||||
msgid "Avatar"
|
||||
msgstr "Profielfoto"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "avatar"
|
||||
msgstr "profielfoto"
|
||||
|
||||
#: forms.py:37
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : "
|
||||
"%(valid_exts_list)s"
|
||||
msgstr ""
|
||||
"%(ext)s is een ongeldig bestandsformaat. Toegestane formaten zijn : "
|
||||
"%(valid_exts_list)s"
|
||||
|
||||
#: forms.py:44
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your file is too big (%(size)s), the maximum allowed size is "
|
||||
"%(max_valid_size)s"
|
||||
msgstr ""
|
||||
"Het bestand is te groot (%(size)s), de maximale groote is %(max_valid_size)s"
|
||||
|
||||
#: forms.py:54
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
|
||||
"%(nb_max_avatars)d."
|
||||
msgstr ""
|
||||
"Er zijn al %(nb_avatars)d profielfoto's, het maximale aantal is "
|
||||
"%(nb_max_avatars)d."
|
||||
|
||||
#: forms.py:71 forms.py:84
|
||||
msgid "Choices"
|
||||
msgstr "Keuzes"
|
||||
|
||||
#: views.py:74
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "De profielfoto is ververst."
|
||||
|
||||
#: views.py:110
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "Profielfoto vernieuwd."
|
||||
|
||||
#: views.py:148
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
msgstr "Profielfoto verwijderd."
|
||||
|
||||
#: templates/avatar/add.html:6 templates/avatar/change.html:6
|
||||
msgid "Your current avatar: "
|
||||
msgstr "De huidige profielfoto:"
|
||||
|
||||
#: templates/avatar/add.html:9 templates/avatar/change.html:9
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr "Er is nog geen profielfoto. Upload een nieuwe."
|
||||
|
||||
#: templates/avatar/add.html:13 templates/avatar/change.html:20
|
||||
msgid "Upload New Image"
|
||||
msgstr "Upload nieuw plaatje"
|
||||
|
||||
#: templates/avatar/change.html:15
|
||||
msgid "Choose new Default"
|
||||
msgstr "Kies nieuwe standaard"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:6
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr "Selecteer de te verwijderen de profielfoto's."
|
||||
|
||||
#: templates/avatar/confirm_delete.html:9
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
|
||||
"\">upload one</a> now."
|
||||
msgstr ""
|
||||
"Er zijn geen profielfoto's om te verwijderen. <a href=\"%(avatar_change_url)s"
|
||||
"\">Upload</a> een nieuwe."
|
||||
|
||||
#: templates/avatar/confirm_delete.html:15
|
||||
msgid "Delete These"
|
||||
msgstr "Verwijder deze"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(avatar_creator)s has updated their avatar %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
"%(avatar_creator)s heeft zijn profielfoto vernieuwd %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> has updated their avatar <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> heeft zijn profielfoto "
|
||||
"vernieuwd <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templates/notification/avatar_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your avatar has been updated. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
"Je profielfoto is vernieuwd. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr "De profielfoto is vernieuwd <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templatetags/avatar_tags.py:51
|
||||
msgid "Default Avatar"
|
||||
msgstr "Standaard profielfoto"
|
||||
Binary file not shown.
|
|
@ -19,27 +19,32 @@ msgstr ""
|
|||
#: forms.py:34
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : %"
|
||||
"(valid_exts_list)s"
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : "
|
||||
"%(valid_exts_list)s"
|
||||
msgstr ""
|
||||
"Extensão informada inválida %(ext)s. Os Formatos permitidos são : "
|
||||
"%(valid_exts_list)s"
|
||||
|
||||
#: forms.py:38
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your file is too big (%(size)s), the maximum allowed size is %"
|
||||
"(max_valid_size)s"
|
||||
msgstr ""
|
||||
"Your file is too big (%(size)s), the maximum allowed size is "
|
||||
"%(max_valid_size)s"
|
||||
msgstr "Arquivo muito grande (%(size)s), o máximo permitido é "
|
||||
"%(max_valid_size)s"
|
||||
|
||||
#: forms.py:44
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is %"
|
||||
"(nb_max_avatars)d."
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
|
||||
"%(nb_max_avatars)d."
|
||||
msgstr ""
|
||||
"Você já possui %(nb_avatars)d fotos. O máximo permitido é "
|
||||
"%(nb_max_avatars)d."
|
||||
|
||||
#: forms.py:56 forms.py:67
|
||||
msgid "Choices"
|
||||
msgstr ""
|
||||
msgstr "Opções"
|
||||
|
||||
#: models.py:75
|
||||
#, python-format
|
||||
|
|
@ -52,7 +57,7 @@ msgstr "Nova foto de perfil enviada com sucesso."
|
|||
|
||||
#: views.py:132
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "Sua foto de perfil foi atualizada com sucesso."
|
||||
msgstr "Sua foto foi atualizada com sucesso."
|
||||
|
||||
#: views.py:166
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
|
|
@ -60,34 +65,35 @@ msgstr "As fotos de perfil selecionadas foram excluídas com sucesso."
|
|||
|
||||
#: templates/avatar/add.html:5 templates/avatar/change.html:5
|
||||
msgid "Your current avatar: "
|
||||
msgstr ""
|
||||
msgstr "Sua foto atual:"
|
||||
|
||||
#: templates/avatar/add.html:8 templates/avatar/change.html:8
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr ""
|
||||
msgstr "Você ainda não possui uma foto de perfil"
|
||||
|
||||
#: templates/avatar/add.html:12 templates/avatar/change.html:19
|
||||
msgid "Upload New Image"
|
||||
msgstr ""
|
||||
msgstr "Enviar foto"
|
||||
|
||||
#: templates/avatar/change.html:14
|
||||
msgid "Choose new Default"
|
||||
msgstr ""
|
||||
msgstr "Escolher padrão"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:5
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr ""
|
||||
msgstr "Por favor, selecione as fotos que você deseja excluir"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:8
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
|
||||
"\">upload one</a> now."
|
||||
msgstr ""
|
||||
msgstr "Você não possui uma foto. Deseja <a href=\"%(avatar_change_url)s"
|
||||
"\">enviar uma agora?</a>"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:14
|
||||
msgid "Delete These"
|
||||
msgstr ""
|
||||
msgstr "Excluir estes"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/full.txt:1
|
||||
#, fuzzy, python-format
|
||||
|
|
@ -95,7 +101,9 @@ msgid ""
|
|||
"%(avatar_creator)s has updated their avatar %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
msgstr "%(avatar_creator)s atualizou a foto do perfil %(avatar)s.\n"
|
||||
"\n"
|
||||
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> atualizou a foto de perfil "
|
||||
"<a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
|
|
@ -114,7 +122,10 @@ msgid ""
|
|||
"Your avatar has been updated. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
msgstr "Sua foto de perfil foi atualizada. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, fuzzy, python-format
|
||||
|
|
@ -131,7 +142,7 @@ msgstr "Foto de Perfil Padrão"
|
|||
#~ msgstr "Foto de Perfil Atualizada"
|
||||
|
||||
#~ msgid "avatar have been updated"
|
||||
#~ msgstr "foto de perfil foi atualizada"
|
||||
#~ msgstr "sua foto de perfil foi atualizada"
|
||||
|
||||
#~ msgid "Friend Updated Avatar"
|
||||
#~ msgstr "Amigo Atualizou Foto de Perfil"
|
||||
|
|
|
|||
Binary file not shown.
BIN
avatar/locale/sk_SK/LC_MESSAGES/django.mo
Normal file
BIN
avatar/locale/sk_SK/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
149
avatar/locale/sk_SK/LC_MESSAGES/django.po
Normal file
149
avatar/locale/sk_SK/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-07-19 19:26+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Tomas Babej <tomasbabej@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: admin.py:19 forms.py:24
|
||||
msgid "Avatar"
|
||||
msgstr "Profilová fotografia"
|
||||
|
||||
#: forms.py:53
|
||||
#, python-format
|
||||
msgid ""
|
||||
"File content is invalid. Detected: %(mimetype)s Allowed content types are: "
|
||||
"%(valid_mime_list)s"
|
||||
msgstr ""
|
||||
"Druh súboru je neplatný. Detekovaný: %(mimetype)s ."
|
||||
"Povolené typy sú: %(valid_mime_list)s"
|
||||
|
||||
#: forms.py:68
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : "
|
||||
"%(valid_exts_list)s"
|
||||
msgstr ""
|
||||
"%(ext)s je neplatný typ súboru. Povolené typy sú: "
|
||||
"%(valid_exts_list)s"
|
||||
|
||||
#: forms.py:75
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your file is too big (%(size)s), the maximum allowed size is "
|
||||
"%(max_valid_size)s"
|
||||
msgstr ""
|
||||
"Váš súbor je príliš veľký (%(size)s), maximálna povolená veľkost "
|
||||
"je %(max_valid_size)s"
|
||||
|
||||
#: forms.py:85
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
|
||||
"%(nb_max_avatars)d."
|
||||
msgstr ""
|
||||
"Už teraz máte %(nb_avatars)d nahratých profilových fotografií. Maximálne "
|
||||
"povolené množstvo je %(nb_max_avatars)d."
|
||||
|
||||
#: forms.py:103 forms.py:116
|
||||
msgid "Available avatars:"
|
||||
msgstr "Profilové fotografie k dispozícií:"
|
||||
|
||||
#: views.py:82
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "Nová profilová fotografia bola úspešne nahraná."
|
||||
|
||||
#: views.py:118
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "Profilová fotografia aktualizovaná."
|
||||
|
||||
#: views.py:156
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
msgstr "Odstránenie zvolených profilových fotografií bolo úspešné."
|
||||
|
||||
#: templates/avatar/add.html:6 templates/avatar/change.html:6
|
||||
msgid "Your current avatar: "
|
||||
msgstr "Vaša súčasná profilová fotografia:"
|
||||
|
||||
#: templates/avatar/add.html:9 templates/avatar/change.html:9
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr "Zatiaľ nemáte žiadnu profilovú fotografiu."
|
||||
|
||||
#: templates/avatar/add.html:13 templates/avatar/change.html:20
|
||||
msgid "Upload New Image"
|
||||
msgstr "Nahrať novú fotografiu"
|
||||
|
||||
#: templates/avatar/change.html:15
|
||||
msgid "Choose new Default"
|
||||
msgstr "Vybrať novú profilovú fotografiu"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:6
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr "Prosím zvoľte fotografie, ktoré si želáte zmazať."
|
||||
|
||||
#: templates/avatar/confirm_delete.html:9
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
|
||||
"\">upload one</a> now."
|
||||
msgstr ""
|
||||
"Nemáte žiadne profilové fotografie, ktoré by ste mohli zmazať. "
|
||||
"Nahrať si ich môžete <a href=\"%(avatar_change_url)s\">tu</a>."
|
||||
|
||||
#: templates/avatar/confirm_delete.html:15
|
||||
msgid "Delete These"
|
||||
msgstr "Zmazať"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(avatar_creator)s has updated their avatar %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
"%(avatar_creator)s si aktualizoval svoju profilovú fotografiu %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
|
||||
#: templates/notification/avatar_friend_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> has updated their avatar <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> si aktualizoval svoju profilovú fotografiu <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templates/notification/avatar_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your avatar has been updated. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
"Vaša profilová fotografia bola aktualizovaná. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr "Aktualizovali ste si vašu profilovú fotografiu <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templatetags/avatar_tags.py:51
|
||||
msgid "Default Avatar"
|
||||
msgstr "Predvolená profilová fotografia"
|
||||
BIN
avatar/locale/zh_CN/LC_MESSAGES/django.mo
Normal file
BIN
avatar/locale/zh_CN/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
135
avatar/locale/zh_CN/LC_MESSAGES/django.po
Normal file
135
avatar/locale/zh_CN/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: django-avatar\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-03-26 16:50+0800\n"
|
||||
"PO-Revision-Date: 2014-03-26 17:08+0800\n"
|
||||
"Last-Translator: Bruce Yang <ayang23@gmail.com>\n"
|
||||
"Language-Team: Bruce Yang <ayang23@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 1.5.7\n"
|
||||
|
||||
#: admin.py:19
|
||||
msgid "Avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: forms.py:24
|
||||
msgid "avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: forms.py:37
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : "
|
||||
"%(valid_exts_list)s"
|
||||
msgstr "%(ext)s 是不正确的文件扩展名。 正确的扩展名为 : %(valid_exts_list)s"
|
||||
|
||||
#: forms.py:44
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your file is too big (%(size)s), the maximum allowed size is "
|
||||
"%(max_valid_size)s"
|
||||
msgstr "上传文件太大 (%(size)s), 允许的最大文件为 %(max_valid_size)s"
|
||||
|
||||
#: forms.py:54
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
|
||||
"%(nb_max_avatars)d."
|
||||
msgstr "您目前有 %(nb_avatars)d 个头像, 最多可以有 %(nb_max_avatars)d 个。"
|
||||
|
||||
#: forms.py:71 forms.py:84
|
||||
msgid "Choices"
|
||||
msgstr "选项"
|
||||
|
||||
#: templates/avatar/add.html:6 templates/avatar/change.html:6
|
||||
msgid "Your current avatar: "
|
||||
msgstr "您当前的头像:"
|
||||
|
||||
#: templates/avatar/add.html:9 templates/avatar/change.html:9
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr "您还没有上传任何头像,请现在上传一个吧。"
|
||||
|
||||
#: templates/avatar/add.html:13 templates/avatar/change.html:20
|
||||
msgid "Upload New Image"
|
||||
msgstr "上传新照片"
|
||||
|
||||
#: templates/avatar/change.html:15
|
||||
msgid "Choose new Default"
|
||||
msgstr "选择默认"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:6
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr "选择要删除的头像。"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:9
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
|
||||
"\">upload one</a> now."
|
||||
msgstr ""
|
||||
"没有头像可以删除. 请 <a href=\"%(avatar_change_url)s\">上传一个新头像</a>。"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:15
|
||||
msgid "Delete These"
|
||||
msgstr "删除"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(avatar_creator)s has updated their avatar %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
"%(avatar_creator)s 更新了头像 %(avatar)s.\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> has updated their avatar <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> 更新了头像 <a href="
|
||||
"\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templates/notification/avatar_updated/full.txt:1
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Your avatar has been updated. %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
msgstr ""
|
||||
"您的头像已经更新。 %(avatar)s\n"
|
||||
"\n"
|
||||
"http://%(current_site)s%(avatar_url)s\n"
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr "您已经更新了头像 <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templatetags/avatar_tags.py:51
|
||||
msgid "Default Avatar"
|
||||
msgstr "默认头像"
|
||||
|
||||
#: views.py:74
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "成功上传头像。"
|
||||
|
||||
#: views.py:110
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "更新头像成功。"
|
||||
|
||||
#: views.py:148
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
msgstr "成功删除头像。"
|
||||
54
avatar/management/commands/migrate_avatars.py
Normal file
54
avatar/management/commands/migrate_avatars.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import os
|
||||
import shutil
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from avatar.conf import settings
|
||||
from avatar.models import Avatar, avatar_file_path
|
||||
from django.core.files import File
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = ("Check if avatar userdirname folder correspond "
|
||||
"with actual userdirname pattern "
|
||||
"affect with options like AVATAR_USERID_AS_USERDIRNAME "
|
||||
"or AVATAR_HASH_USERDIRNAMES.")
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
# define path to avatar folders
|
||||
avatar_path = os.path.join(settings.MEDIA_URL,
|
||||
settings.AVATAR_STORAGE_DIR)
|
||||
|
||||
have_change = False
|
||||
for avatar in Avatar.objects.all():
|
||||
# get actuel path of avatar
|
||||
new_path = os.path.join(settings.MEDIA_URL,
|
||||
avatar_file_path(avatar))
|
||||
original_path = avatar.avatar.url
|
||||
if new_path != original_path:
|
||||
print("Move Avatar id=%s from %s to %s." % (avatar.id,
|
||||
original_path,
|
||||
new_path))
|
||||
|
||||
# if different path: we copy + generate new thumbnails + clean
|
||||
media_path = original_path.replace(settings.MEDIA_URL, "")
|
||||
media_path = os.path.join(settings.MEDIA_ROOT, media_path)
|
||||
# copy original avatar into new folder
|
||||
avatar.avatar.save(os.path.basename(new_path),
|
||||
File(open(media_path)))
|
||||
avatar.save()
|
||||
|
||||
# delete old folder and thumbnails
|
||||
i = original_path.find('/', len(avatar_path) + 1)
|
||||
folder = original_path[0:i]
|
||||
if folder[0:1] == "/":
|
||||
folder = folder[1:]
|
||||
folder = os.path.join(settings.BASE_DIR, folder)
|
||||
print("Delete useless folder %s" % folder)
|
||||
shutil.rmtree(folder)
|
||||
have_change = True
|
||||
|
||||
# generate all default thumbnails in new folder
|
||||
if have_change:
|
||||
call_command('rebuild_avatars')
|
||||
else:
|
||||
print("No change ;)")
|
||||
|
|
@ -20,16 +20,19 @@ except ImportError:
|
|||
now = datetime.datetime.now
|
||||
|
||||
|
||||
avatar_storage = get_storage_class(settings.AVATAR_STORAGE)()
|
||||
avatar_storage = get_storage_class(settings.AVATAR_STORAGE)(**settings.AVATAR_STORAGE_PARAMS)
|
||||
|
||||
|
||||
def avatar_file_path(instance=None, filename=None, size=None, ext=None):
|
||||
tmppath = [settings.AVATAR_STORAGE_DIR]
|
||||
userdirname = get_username(instance.user)
|
||||
if settings.AVATAR_USERID_AS_USERDIRNAME:
|
||||
userdirname = str(instance.user_id)
|
||||
if settings.AVATAR_HASH_USERDIRNAMES:
|
||||
tmp = hashlib.md5(get_username(instance.user)).hexdigest()
|
||||
tmppath.extend([tmp[0], tmp[1], get_username(instance.user)])
|
||||
tmp = hashlib.md5(userdirname).hexdigest()
|
||||
tmppath.extend([tmp[0], tmp[1], userdirname])
|
||||
else:
|
||||
tmppath.append(get_username(instance.user))
|
||||
tmppath.append(userdirname)
|
||||
if not filename:
|
||||
# Filename already stored in database
|
||||
filename = instance.avatar.name
|
||||
|
|
@ -88,12 +91,38 @@ class Avatar(models.Model):
|
|||
def thumbnail_exists(self, size):
|
||||
return self.avatar.storage.exists(self.avatar_name(size))
|
||||
|
||||
def transpose_image(self, image):
|
||||
"""
|
||||
Transpose based on EXIF information.
|
||||
Borrowed from django-imagekit:
|
||||
imagekit.processors.Transpose
|
||||
"""
|
||||
EXIF_ORIENTATION_STEPS = {
|
||||
1: [],
|
||||
2: ['FLIP_LEFT_RIGHT'],
|
||||
3: ['ROTATE_180'],
|
||||
4: ['FLIP_TOP_BOTTOM'],
|
||||
5: ['ROTATE_270', 'FLIP_LEFT_RIGHT'],
|
||||
6: ['ROTATE_270'],
|
||||
7: ['ROTATE_90', 'FLIP_LEFT_RIGHT'],
|
||||
8: ['ROTATE_90'],
|
||||
}
|
||||
try:
|
||||
orientation = image._getexif()[0x0112]
|
||||
ops = EXIF_ORIENTATION_STEPS[orientation]
|
||||
except:
|
||||
ops = []
|
||||
for method in ops:
|
||||
image = image.transpose(getattr(Image, method))
|
||||
return image
|
||||
|
||||
def create_thumbnail(self, size, quality=None):
|
||||
# invalidate the cache of the thumbnail with the given size first
|
||||
invalidate_cache(self.user, size)
|
||||
try:
|
||||
orig = self.avatar.storage.open(self.avatar.name, 'rb')
|
||||
image = Image.open(orig)
|
||||
image = self.transpose_image(image)
|
||||
quality = quality or settings.AVATAR_THUMB_QUALITY
|
||||
w, h = image.size
|
||||
if w != size or h != size:
|
||||
|
|
@ -103,7 +132,7 @@ class Avatar(models.Model):
|
|||
else:
|
||||
diff = int((h - w) / 2)
|
||||
image = image.crop((0, diff, w, h - diff))
|
||||
if image.mode != "RGB":
|
||||
if image.mode not in ("RGB", "RGBA"):
|
||||
image = image.convert("RGB")
|
||||
image = image.resize((size, size), settings.AVATAR_RESIZE_METHOD)
|
||||
thumb = six.BytesIO()
|
||||
|
|
@ -131,7 +160,8 @@ class Avatar(models.Model):
|
|||
|
||||
|
||||
def invalidate_avatar_cache(sender, instance, **kwargs):
|
||||
invalidate_cache(instance.user)
|
||||
if hasattr(instance, 'user'):
|
||||
invalidate_cache(instance.user)
|
||||
|
||||
|
||||
def create_default_thumbnails(sender, instance, created=False, **kwargs):
|
||||
|
|
@ -142,10 +172,11 @@ def create_default_thumbnails(sender, instance, created=False, **kwargs):
|
|||
|
||||
|
||||
def remove_avatar_images(instance=None, **kwargs):
|
||||
for size in settings.AVATAR_AUTO_GENERATE_SIZES:
|
||||
if instance.thumbnail_exists(size):
|
||||
instance.avatar.storage.delete(instance.avatar_name(size))
|
||||
instance.avatar.storage.delete(instance.avatar.name)
|
||||
if hasattr(instance, 'user'):
|
||||
for size in settings.AVATAR_AUTO_GENERATE_SIZES:
|
||||
if instance.thumbnail_exists(size):
|
||||
instance.avatar.storage.delete(instance.avatar_name(size))
|
||||
instance.avatar.storage.delete(instance.avatar.name)
|
||||
|
||||
|
||||
signals.post_save.connect(create_default_thumbnails, sender=Avatar)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ def avatar_url(user, size=settings.AVATAR_DEFAULT_SIZE):
|
|||
params = {'s': str(size)}
|
||||
if settings.AVATAR_GRAVATAR_DEFAULT:
|
||||
params['d'] = settings.AVATAR_GRAVATAR_DEFAULT
|
||||
path = "%s/?%s" % (hashlib.md5(force_bytes(user.email)).hexdigest(),
|
||||
urlencode(params))
|
||||
path = "%s/?%s" % (hashlib.md5(force_bytes(getattr(user,
|
||||
settings.AVATAR_GRAVATAR_FIELD))).hexdigest(), urlencode(params))
|
||||
return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path)
|
||||
|
||||
return get_default_avatar_url()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ urlpatterns = patterns('avatar.views',
|
|||
url(r'^add/$', 'add', name='avatar_add'),
|
||||
url(r'^change/$', 'change', name='avatar_change'),
|
||||
url(r'^delete/$', 'delete', name='avatar_delete'),
|
||||
url(r'^render_primary/(?P<user>[\w\d\.\-_]{3,30})/(?P<size>[\d]+)/$', 'render_primary', name='avatar_render_primary'),
|
||||
url(r'^render_primary/(?P<user>[\w\d\@\.\-_]{3,30})/(?P<size>[\d]+)/$', 'render_primary', name='avatar_render_primary'),
|
||||
url(r'^list/(?P<username>[\+\w\@\.]+)/$', 'avatar_gallery', name='avatar_gallery'),
|
||||
url(r'^list/(?P<username>[\+\w\@\.]+)/(?P<id>[\d]+)/$', 'avatar', name='avatar'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import os
|
||||
import uuid
|
||||
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils import six
|
||||
|
|
@ -69,7 +72,12 @@ def add(request, extra_context=None, next_override=None,
|
|||
if upload_avatar_form.is_valid():
|
||||
avatar = Avatar(user=request.user, primary=True)
|
||||
image_file = request.FILES['avatar']
|
||||
avatar.avatar.save(image_file.name, image_file)
|
||||
|
||||
filename_parts = os.path.splitext(image_file.name)
|
||||
extension = filename_parts[1]
|
||||
filename = '%s%s' % (uuid.uuid4(), extension)
|
||||
|
||||
avatar.avatar.save(filename, image_file)
|
||||
avatar.save()
|
||||
messages.success(request, _("Successfully uploaded a new avatar."))
|
||||
avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar)
|
||||
|
|
|
|||
|
|
@ -32,36 +32,40 @@ that are required. A minimal integration can work like this:
|
|||
|
||||
1. List this application in the ``INSTALLED_APPS`` portion of your settings
|
||||
file. Your settings file will look something like::
|
||||
|
||||
|
||||
INSTALLED_APPS = (
|
||||
# ...
|
||||
'avatar',
|
||||
)
|
||||
|
||||
2. Add the avatar urls to the end of your root urlconf. Your urlconf
|
||||
2. Update your database::
|
||||
|
||||
python manage.py syncdb
|
||||
|
||||
3. Add the avatar urls to the end of your root urlconf. Your urlconf
|
||||
will look something like::
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# ...
|
||||
(r'^avatar/', include('avatar.urls')),
|
||||
)
|
||||
|
||||
3. Somewhere in your template navigation scheme, link to the change avatar
|
||||
4. Somewhere in your template navigation scheme, link to the change avatar
|
||||
page::
|
||||
|
||||
|
||||
<a href="{% url 'avatar_change' %}">Change your avatar</a>
|
||||
|
||||
4. Wherever you want to display an avatar for a user, first load the avatar
|
||||
5. Wherever you want to display an avatar for a user, first load the avatar
|
||||
template tags::
|
||||
|
||||
|
||||
{% load avatar_tags %}
|
||||
|
||||
|
||||
Then, use the ``avatar`` tag to display an avatar of a default size::
|
||||
|
||||
|
||||
{% avatar user %}
|
||||
|
||||
|
||||
Or specify a size (in pixels) explicitly::
|
||||
|
||||
|
||||
{% avatar user 65 %}
|
||||
|
||||
Template tags and filter
|
||||
|
|
@ -94,7 +98,7 @@ Global Settings
|
|||
There are a number of settings available to easily customize the avatars that
|
||||
appear on the site. Listed below are those settings:
|
||||
|
||||
AUTO_GENERATE_AVATAR_SIZES
|
||||
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,)``
|
||||
|
|
@ -108,6 +112,12 @@ AVATAR_STORAGE_DIR
|
|||
non-filesystem storage device, this will simply be appended to the beginning
|
||||
of the file name.
|
||||
|
||||
AVATAR_USERID_AS_USERDIRNAME
|
||||
By default, ``User.username`` will be use as directory name under ``AVATAR_STORAGE_DIR`.
|
||||
If set to ``True``, ``User.id`` wil be use instead of ``User.username``.
|
||||
Usefull if user can change his username into app to avoid duplicate content.
|
||||
Defaults to ``False``.
|
||||
|
||||
AVATAR_GRAVATAR_BACKUP
|
||||
A boolean determining whether to default to the Gravatar service if no
|
||||
``Avatar`` instance is found in the system for the given user. Defaults to
|
||||
|
|
@ -117,13 +127,36 @@ 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.
|
||||
|
||||
AVATAR_GRAVATAR_FIELD
|
||||
The name of the user's field containing the gravatar email. Defaults to
|
||||
``email``. If you put, for example, ``gravatar``, django-avatar will get the
|
||||
user's gravatar in ``user.gravatar``.
|
||||
|
||||
AVATAR_ALLOWED_MIMETYPES
|
||||
Limit allowed avatar image uploads by their actual content payload and what
|
||||
image codecs we wish to support. This limits website user content site
|
||||
attack vectors against image codec buffer overflow and similar bugs.
|
||||
`You must have python-imaging library installed <https://github.com/ahupp/python-magic>`_.
|
||||
Suggested safe setting: ``("image/png", "image/gif", "image/jpeg")``.
|
||||
When enabled you'll get the following error on the form upload *File content is invalid.
|
||||
Detected: image/tiff Allowed content types are: image/png, image/gif, image/jpg*.
|
||||
|
||||
|
||||
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 ``AUTO_GENERATE_AVATAR_SIZES``
|
||||
setting.
|
||||
This application does include two management command: ``rebuild_avatars`` and ``migrate_avatars``.
|
||||
|
||||
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 ``AUTO_GENERATE_AVATAR_SIZES``
|
||||
setting.
|
||||
|
||||
migrate_avatars
|
||||
It takes no arguments and, when run, check all avatar userdirname folders.
|
||||
If folder doesn't correspond with actual userdirname pattern (affect with options like
|
||||
``AVATAR_USERID_AS_USERDIRNAME`` or ``AVATAR_HASH_USERDIRNAMES``), it move avatar files to the right place,
|
||||
call ``rebuild_avatars`` and delete useless folders.
|
||||
|
||||
|
||||
.. _pip: http://www.pip-installer.org/
|
||||
|
|
|
|||
144
docs/usage.txt
Normal file
144
docs/usage.txt
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
Using django-avatar
|
||||
===================
|
||||
|
||||
|
||||
Basics
|
||||
------
|
||||
|
||||
To integrate ``django-avatar`` with your site, there are relatively few things
|
||||
that are required. A minimal integration can work like this:
|
||||
|
||||
1. List this application in the ``INSTALLED_APPS`` portion of your settings
|
||||
file. Your settings file will look something like::
|
||||
|
||||
INSTALLED_APPS = (
|
||||
# ...
|
||||
'avatar',
|
||||
)
|
||||
|
||||
2. Add the pagination urls to the end of your root urlconf. Your urlconf
|
||||
will look something like::
|
||||
|
||||
urlpatterns = patterns('',
|
||||
# ...
|
||||
(r'^admin/(.*)', admin.site.root),
|
||||
(r'^avatar/', include('avatar.urls')),
|
||||
)
|
||||
|
||||
3. Somewhere in your template navigation scheme, link to the change avatar
|
||||
page::
|
||||
|
||||
<a href="{% url 'avatar_change' %}">Change your avatar</a>
|
||||
|
||||
4. Wherever you want to display an avatar for a user, first load the avatar
|
||||
template tags::
|
||||
|
||||
{% load avatar_tags %}
|
||||
|
||||
Then, use the ``avatar`` tag to display an avatar of a default size::
|
||||
|
||||
{% avatar user %}
|
||||
|
||||
Or specify a size (in pixels) explicitly::
|
||||
|
||||
{% avatar user 65 %}
|
||||
|
||||
5. Optionally customize ``avatar/change.html`` and
|
||||
``avatar/confirm_delete.html`` to conform to your site's look and feel.
|
||||
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
There are only two views for this application: one for changing a user's avatar,
|
||||
and another for deleting a user's avatar.
|
||||
|
||||
Changing an avatar
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The actual view function is located at ``avatar.views.change``, and this can
|
||||
be referenced by the url name ``avatar_change``. It takes two keyword
|
||||
arguments: ``extra_context`` and ``next_override``. If ``extra_context`` is
|
||||
provided, that context will be placed into the template's context.
|
||||
|
||||
If ``next_override`` is provided, the user will be redirected to the specified
|
||||
URL after form submission. Otherwise the user will be redirected to the URL
|
||||
specified in the ``next`` parameter in ``request.POST``. If ``request.POST``
|
||||
has no ``next`` parameter, ``request.GET`` will be searched. If ``request.GET``
|
||||
has no ``next`` parameter, the ``HTTP_REFERER`` header will be inspected. If
|
||||
that header does not exist, the user will be redirected back to the current URL.
|
||||
|
||||
Deleting an avatar
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The actual view function is located at ``avatar.views.delete``, and this can be
|
||||
referenced by the url name ``avatar_delete``. It takes the same two keyword
|
||||
arguments as ``avatar.views.change`` and follows the same redirection rules
|
||||
as well.
|
||||
|
||||
|
||||
Template Tags
|
||||
-------------
|
||||
|
||||
To begin using these template tags, you must first load the tags into the
|
||||
template rendering system:
|
||||
|
||||
{% load avatar_tags %}
|
||||
|
||||
``{% avatar_url user [size in pixels] %}``
|
||||
Renders the URL of the avatar for the given user. User can be either a
|
||||
``django.contrib.auth.models.User`` object instance or a username.
|
||||
|
||||
``{% avatar user [size in pixels] %}``
|
||||
Renders an HTML ``img`` tag for the given user for the specified size. User
|
||||
can be either a ``django.contrib.auth.models.User`` object instance or a
|
||||
username.
|
||||
|
||||
``{% render_avatar avatar [size in pixels] %}``
|
||||
Given an actual ``avatar.models.Avatar`` object instance, renders an HTML
|
||||
``img`` tag to represent that avatar at the requested size.
|
||||
|
||||
|
||||
Global Settings
|
||||
---------------
|
||||
|
||||
There are a number of settings available to easily customize the avatars that
|
||||
appear on the site. Listed below are those settings:
|
||||
|
||||
AUTO_GENERATE_AVATAR_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_RESIZE_METHOD
|
||||
The method to use when resizing images, based on the options available in
|
||||
PIL. Defaults to ``Image.ANTIALIAS``.
|
||||
|
||||
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.
|
||||
|
||||
AVATAR_GRAVATAR_BACKUP
|
||||
A boolean determining whether to default to the Gravatar service if no
|
||||
``Avatar`` instance is found in the system for the given user. 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.
|
||||
|
||||
AVATAR_ALLOWED_MIMETYPES
|
||||
Limit allowed avatar image uploads by their actual content payload and what image codecs we wish to support.
|
||||
This limits website user content site attack vectors against image codec buffer overflow and similar bugs.
|
||||
`You must have python-imaging library installed <https://github.com/ahupp/python-magic>`_.
|
||||
Suggested safe setting: ``("image/png", "image/gif", "image/jpeg")``.
|
||||
When enabled you'll get the following error on the form upload *File content is invalid. Detected: image/tiff Allowed content types are: image/png, image/gif, image/jpg*.
|
||||
|
||||
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 ``AUTO_GENERATE_AVATAR_SIZES``
|
||||
setting.
|
||||
BIN
tests/data/django.png
Normal file
BIN
tests/data/django.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
tests/data/django_pony_cmyk.jpg
Normal file
BIN
tests/data/django_pony_cmyk.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
tests/data/test.tiff
Normal file
BIN
tests/data/test.tiff
Normal file
Binary file not shown.
|
|
@ -2,11 +2,17 @@ import os.path
|
|||
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from avatar.conf import settings
|
||||
from avatar.util import get_primary_avatar, get_user_model
|
||||
from avatar.models import Avatar
|
||||
from PIL import Image
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
dir(Image) # Placate PyFlakes
|
||||
except ImportError:
|
||||
import Image
|
||||
|
||||
|
||||
def upload_helper(o, filename):
|
||||
|
|
@ -40,6 +46,13 @@ class AvatarUploadTests(TestCase):
|
|||
avatar = get_primary_avatar(self.user)
|
||||
self.assertNotEqual(avatar, None)
|
||||
|
||||
def testUnsupportedImageFormatUpload(self):
|
||||
""" Check with python-magic that we detect corrupted / unapprovd image files correctly """
|
||||
response = upload_helper(self, "test.tiff")
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 0) # Redirect only if it worked
|
||||
self.failIfEqual(response.context['upload_avatar_form'].errors, {})
|
||||
|
||||
def testImageWithoutExtension(self):
|
||||
# use with AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
|
||||
response = upload_helper(self, "imagefilewithoutext")
|
||||
|
|
@ -126,3 +139,16 @@ class AvatarUploadTests(TestCase):
|
|||
# def testChangePrimaryAvatar
|
||||
# def testDeleteThumbnailAndRecreation
|
||||
# def testAutomaticThumbnailCreation
|
||||
|
||||
@override_settings(AVATAR_THUMB_FORMAT='png')
|
||||
def testAutomaticThumbnailCreationRGBA(self):
|
||||
upload_helper(self, "django.png")
|
||||
avatar = get_primary_avatar(self.user)
|
||||
image = Image.open(avatar.avatar.storage.open(avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), 'rb'))
|
||||
self.assertEqual(image.mode, 'RGBA')
|
||||
|
||||
def testAutomaticThumbnailCreationCMYK(self):
|
||||
upload_helper(self, "django_pony_cmyk.jpg")
|
||||
avatar = get_primary_avatar(self.user)
|
||||
image = Image.open(avatar.avatar.storage.open(avatar.avatar_name(settings.AVATAR_DEFAULT_SIZE), 'rb'))
|
||||
self.assertEqual(image.mode, 'RGB')
|
||||
|
|
|
|||
Loading…
Reference in a new issue