Merge with upstream master v4.0.0

This commit is contained in:
Mario Maccarini 2017-09-13 12:50:34 +02:00
commit 144e0576d7
60 changed files with 2112 additions and 439 deletions

25
.coveragerc Normal file
View file

@ -0,0 +1,25 @@
[report]
exclude_lines =
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
except ImportError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
omit =
avatar/migrations/*
show_missing = True
precision = 2
[html]
directory = htmlcov/

5
.gitignore vendored
View file

@ -8,4 +8,7 @@ dist/
*.egg-info/
avatars
.coverage
docs/_build
docs/_build
htmlcov/
*.sqlite3
test_proj/media

View file

@ -1,21 +1,25 @@
language: python
python:
- 2.6
- 2.7
- 3.2
- 3.3
- 3.4
- 3.5
- 3.6
before_install:
- pip install coveralls
install:
- pip install -e .
- pip install -r tests/requirements.txt
- pip install https://github.com/django/django/archive/${DJANGO}.zip#egg=django
- pip install Django==${DJANGO}
script: make test
env:
- DJANGO=1.4.7
- DJANGO=1.5.3
- DJANGO=stable/1.6.x
- DJANGO=1.9.13
- DJANGO=1.10.7
- DJANGO=1.11.1
matrix:
exclude:
- python: 3.2
env: DJANGO=1.4.7
- python: 3.3
env: DJANGO=1.4.7
- python: 3.6
env: DJANGO=1.9.13
- python: 3.6
env: DJANGO=1.10.7
after_success:
- coveralls

54
CHANGELOG.rst Normal file
View file

@ -0,0 +1,54 @@
Changelog
=========
* 4.0.0 (May 27, 2017)
* **Backwards incompatible:** Added ``AVATAR_PROVIDERS`` setting. Avatar providers are classes that return an avatar URL for a given user.
* Added ``verbose_name`` to ``Avatar`` model fields.
* Added the ability to override the ``alt`` attribute using the ``avatar`` template tag.
* Added Italian translations.
* Improved German translations.
* Fixed bug where ``rebuild_avatars`` would fail on Django 1.10+.
* Added Django 1.11 support.
* Added Python 3.6 support.
* Removed Django 1.7 and 1.8 support.
* Removed Python 3.3 support.
* 3.1.0 (September 10, 2016)
* Added the ability to override templates using ``AVATAR_ADD_TEMPLATE``, ``AVATAR_CHANGE_TEMPLATE``, and ``AVATAR_DELETE_TEMPLATE``.
* Added the ability to pass additional HTML attributes using the ``{% avatar %}`` template tag.
* Fixed unused verbosity setting in ``rebuild_avatars.py``.
* Added Django 1.10 support
* Removed Python 3.2 support
* 3.0.0 (February 26, 2016):
* Added the ability to hide usernames/emails from avatar URLs.
* Added the ability to use a Facebook Graph avatar as a backup.
* Added a way to customize where avatars are stored.
* Added a setting to disable the avatar cache.
* Updated thumbnail creation to preserve RGBA.
* Fixed issue where ``render_primary`` would not work if username/email was greater than 30 characters.
* Fixed issue where cache was not invalidated after updating avatar
* **Backwards Incompatible:** Renamed the ``avatar.util`` module to ``avatar.utils``.
* 2.2.1 (January 11, 2016)
* Added AVATAR_GRAVATAR_FIELD setting to define the user field to get the gravatar email.
* Improved Django 1.9/1.10 compatibility
* Improved Brazilian translations
* 2.2.0 (December 2, 2015)
* Added Python 3.5 support
* Added Django 1.9 support
* Removed Python 2.6 support
* Removed Django 1.4, 1.5, and 1.6 support
* 2.1.1 (August 10, 2015)
* Added Polish locale
* Fixed RemovedInDjango19Warning warnings
* 2.1 (May 2, 2015)
* Django 1.7 and 1.8 support
* Add South and Django migrations
* Changed Gravatar link to use HTTPS by default
* Fixed a bug where the admin avatar list page would only show a user's primary avatar
* Updated render_primary view to accept usernames with @ signs in them
* Updated translations (added Dutch, Japanese, and Simple Chinese)

View file

@ -1,4 +1,4 @@
This application was originally written by Eric Florenzano.
It is now maintained by Jannis Leidel and a league of awesome contributors.
It is now maintained by Grant McConnaughey and a league of awesome contributors.
See the full list here: https://github.com/jezdez/django-avatar/graphs/contributors
See the full list here: https://github.com/grantmcconnaughey/django-avatar/graphs/contributors

View file

@ -5,5 +5,14 @@ export PYTHONPATH=.
test:
flake8 avatar --ignore=E124,E501,E127,E128
coverage run --branch --source=avatar `which django-admin.py` test tests
coverage run --source=avatar `which django-admin.py` test tests
coverage report
publish: clean
python setup.py sdist
twine upload dist/*
clean:
rm -vrf ./build ./dist ./*.egg-info
find . -name '*.pyc' -delete
find . -name '*.tgz' -delete

View file

@ -2,8 +2,25 @@
django-avatar
=============
.. image:: https://secure.travis-ci.org/jezdez/django-avatar.png
:target: http://travis-ci.org/jezdez/django-avatar
.. image:: https://badge.fury.io/py/django-avatar.svg
:target: https://badge.fury.io/py/django-avatar
:alt: PyPI badge
.. image:: https://readthedocs.org/projects/django-avatar/badge/?version=latest
:target: http://django-avatar.readthedocs.org/en/latest/?badge=latest
:alt: Documentation Status
.. image:: https://travis-ci.org/grantmcconnaughey/django-avatar.svg?branch=master
:target: https://travis-ci.org/grantmcconnaughey/django-avatar
:alt: Travis CI Build Status
.. image:: https://coveralls.io/repos/grantmcconnaughey/django-avatar/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/grantmcconnaughey/django-avatar?branch=master
:alt: Coverage
.. image:: https://lintly.com/gh/grantmcconnaughey/django-avatar/badge.svg
:target: https://lintly.com/gh/grantmcconnaughey/django-avatar/
:alt: Lintly
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.

View file

@ -1 +1 @@
__version__ = '2.0'
__version__ = '4.0.0'

View file

@ -1,10 +1,11 @@
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
from avatar.utils import get_user_model
class AvatarAdmin(admin.ModelAdmin):
@ -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

View file

@ -8,9 +8,11 @@ class AvatarConf(AppConf):
DEFAULT_SIZE = 80
RESIZE_METHOD = Image.ANTIALIAS
STORAGE_DIR = 'avatars'
GRAVATAR_BASE_URL = 'http://www.gravatar.com/avatar/'
GRAVATAR_BACKUP = True
PATH_HANDLER = 'avatar.models.avatar_path_handler'
GRAVATAR_BASE_URL = 'https://www.gravatar.com/avatar/'
GRAVATAR_FIELD = 'email'
GRAVATAR_DEFAULT = None
AVATAR_GRAVATAR_FORCEDEFAULT = False
DEFAULT_URL = 'avatar/img/default.jpg'
MAX_AVATARS_PER_USER = 42
MAX_SIZE = 1024 * 1024
@ -18,12 +20,24 @@ class AvatarConf(AppConf):
THUMB_QUALITY = 85
HASH_FILENAMES = False
HASH_USERDIRNAMES = False
EXPOSE_USERNAMES = True
ALLOWED_FILE_EXTS = None
CACHE_TIMEOUT = 60 * 60
STORAGE = settings.DEFAULT_FILE_STORAGE
CLEANUP_DELETED = False
AUTO_GENERATE_SIZES = (DEFAULT_SIZE,)
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, 'AUTO_GENERATE_AVATAR_SIZES',
return value or getattr(settings, 'AVATAR_AUTO_GENERATE_SIZES',
(self.DEFAULT_SIZE,))

View file

@ -3,66 +3,74 @@
# 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"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-28 10:59+0200\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"
"POT-Creation-Date: 2016-09-14 16:37+0200\n"
"PO-Revision-Date: 2016-09-14 14:34+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\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"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.9\n"
#: forms.py:34
#: admin.py:26
msgid "Avatar"
msgstr "Avatar"
#: forms.py:24 models.py:84 models.py:97
msgid "avatar"
msgstr "avatar"
#: forms.py:37
#, 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 ""
"%(ext)s ist ein ungültiges Dateiformat. Erlaubte Formate sind: %"
"(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 ""
"Die Datei ist zu groß (%(size)s), die Maximalgröße ist %(max_valid_size)s"
"%(ext)s ist ein ungültiges Dateiformat. Erlaubte Formate sind: "
"%(valid_exts_list)s"
#: forms.py:44
#, python-format
msgid ""
"You already have %(nb_avatars)d avatars, and the maximum allowed is %"
"(nb_max_avatars)d."
"Your file is too big (%(size)s), the maximum allowed size is "
"%(max_valid_size)s"
msgstr ""
"Sie haben bereits %(nb_avatars)d Avatarbilder hochgeladen. Das maximale "
"Die Datei ist zu groß (%(size)s), die Maximalgröße ist %(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 ""
"Sie haben bereits %(nb_avatars)d Avatarbilder hochgeladen. Die maximale "
"Anzahl ist %(nb_max_avatars)d."
#: forms.py:56 forms.py:67
#: forms.py:71 forms.py:84
msgid "Choices"
msgstr ""
msgstr "Auswahl"
#: models.py:75
#, python-format
msgid "Avatar for %s"
msgstr "Avatar für %s"
#: models.py:77
msgid "user"
msgstr "Benutzer"
#: views.py:73 views.py:95
msgid "Successfully uploaded a new avatar."
msgstr "Erfolgreich einen neuen Avatar hochgeladen."
#: models.py:80
msgid "primary"
msgstr "primär"
#: views.py:132
msgid "Successfully updated your avatar."
msgstr "Erfolgreich Ihren Avatar aktualisiert."
#: models.py:91
msgid "uploaded at"
msgstr "hochgeladen am"
#: views.py:166
msgid "Successfully deleted the requested avatars."
msgstr "Erfolgreich den Avatar gelöscht."
#: models.py:98
msgid "avatars"
msgstr "Avatare"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
@ -83,7 +91,7 @@ msgstr "Standard auswählen"
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "Bitte wählen Sie die Avatar aus, die Sie löschen möchten."
msgstr "Bitte wählen Sie den Avatar aus, den Sie löschen möchten."
#: templates/avatar/confirm_delete.html:8
#, python-format
@ -99,14 +107,15 @@ msgid "Delete These"
msgstr "Auswahl löschen"
#: templates/notification/avatar_friend_updated/full.txt:1
#, fuzzy, python-format
#, python-format
msgid ""
"%(avatar_creator)s has updated their avatar %(avatar)s.\n"
"\n"
"http://%(current_site)s%(avatar_url)s\n"
msgstr ""
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> hat den Avatar aktualisiert "
"<a href=\"%(avatar_url)s\">%(avatar)s</a>."
"%(avatar_creator)s hat seinen/ihren Avatar %(avatar)s aktualisiert.\n"
"\n"
"http://%(current_site)s%(avatar_url)s\n"
#: templates/notification/avatar_friend_updated/notice.html:2
#, python-format
@ -114,8 +123,8 @@ 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> hat den Avatar aktualisiert "
"<a href=\"%(avatar_url)s\">%(avatar)s</a>."
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> hat ihren/seinen Avatar "
"aktualisiert <a href=\"%(avatar_url)s\">%(avatar)s</a>."
#: templates/notification/avatar_updated/full.txt:1
#, python-format
@ -124,6 +133,9 @@ msgid ""
"\n"
"http://%(current_site)s%(avatar_url)s\n"
msgstr ""
"Ihr Avatar wurde aktualisiert. %(avatar)s\n"
"\n"
"http://%(current_site)s%(avatar_url)s\n"
#: templates/notification/avatar_updated/notice.html:2
#, python-format
@ -132,10 +144,22 @@ msgstr ""
"Sie haben Ihren Avatar aktualisiert <a href=\"%(avatar_url)s\">%(avatar)s</"
"a>."
#: templatetags/avatar_tags.py:45
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Standard-Avatar"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Ein neuer Avatar wurde erfolgreich hochgeladen."
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Ihr Avatar wurde erfolgreich aktualisiert."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Ihr Avatar wurde erfolgreich gelöscht."
#~ msgid "Avatar Updated"
#~ msgstr "Avatar aktualisiert"

View file

@ -7,26 +7,26 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.0a10\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-08-26 23:53-0500\n"
"POT-Creation-Date: 2016-09-14 16:37+0200\n"
"PO-Revision-Date: 2013-08-27 00:21-0600\n"
"Last-Translator: David Loaiza M. <david@zooluciones.com>\n"
"Language-Team: es <david.loaiza@gmail.com>\n"
"Language: es\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.7\n"
"Language: es\n"
#: admin.py:19
#: admin.py:26
msgid "Avatar"
msgstr "Avatar"
#: forms.py:23
#: forms.py:24 models.py:84 models.py:97
msgid "avatar"
msgstr "avatar"
#: forms.py:35
#: forms.py:37
#, python-format
msgid ""
"%(ext)s is an invalid file extension. Authorized extensions are : "
@ -35,7 +35,7 @@ msgstr ""
"%(ext)s es una extensión de archivo inválida. Las extensiones de archivo "
"autorizadas son: %(valid_exts_list)s"
#: forms.py:39
#: forms.py:44
#, python-format
msgid ""
"Your file is too big (%(size)s), the maximum allowed size is "
@ -44,7 +44,7 @@ msgstr ""
"Su archivo es muy grande (%(size)s), el tamaño máximo permitido es "
"%(max_valid_size)s"
#: forms.py:49
#: forms.py:54
#, python-format
msgid ""
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
@ -53,43 +53,49 @@ msgstr ""
"Usted ya tiene %(nb_avatars)d avatares, y el máximo permitido es "
"%(nb_max_avatars)d."
#: forms.py:65 forms.py:77
#: forms.py:71 forms.py:84
msgid "Choices"
msgstr "Opciones"
#: views.py:71
msgid "Successfully uploaded a new avatar."
msgstr "Se ha subido correctamente un nuevo avatar"
#: models.py:77
msgid "user"
msgstr ""
#: views.py:106
msgid "Successfully updated your avatar."
msgstr "Se ha actualizado correctamente su avatar."
#: models.py:80
msgid "primary"
msgstr ""
#: views.py:141
msgid "Successfully deleted the requested avatars."
msgstr "Se han eliminado correctamente los avatares solicitados."
#: models.py:91
msgid "uploaded at"
msgstr ""
#: templates/avatar/add.html:6 templates/avatar/change.html:6
#: models.py:98
#, fuzzy
#| msgid "avatar"
msgid "avatars"
msgstr "avatar"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "Su avatar actual:"
#: templates/avatar/add.html:9 templates/avatar/change.html:9
#: templates/avatar/add.html:8 templates/avatar/change.html:8
msgid "You haven't uploaded an avatar yet. Please upload one now."
msgstr "No ha subido un avatar aún. Por favor, suba uno ahora."
#: templates/avatar/add.html:13 templates/avatar/change.html:20
#: templates/avatar/add.html:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "Subir Nueva Imagen"
#: templates/avatar/change.html:15
#: templates/avatar/change.html:14
msgid "Choose new Default"
msgstr "Elige nuevo predeterminado"
#: templates/avatar/confirm_delete.html:6
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "Por favor seleccione los avatares que le gustaría eliminar."
#: templates/avatar/confirm_delete.html:9
#: templates/avatar/confirm_delete.html:8
#, python-format
msgid ""
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
@ -98,7 +104,7 @@ msgstr ""
"No tiene avatares para borrar. Por favor <a href=\"%(avatar_change_url)s"
"\">suba uno</a> ahora."
#: templates/avatar/confirm_delete.html:15
#: templates/avatar/confirm_delete.html:14
msgid "Delete These"
msgstr "Eliminar Estos"
@ -138,6 +144,18 @@ msgstr ""
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
msgstr "Ha actualizado su avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
#: templatetags/avatar_tags.py:57
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Avatar Predeterminado"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Se ha subido correctamente un nuevo avatar"
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Se ha actualizado correctamente su avatar."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Se han eliminado correctamente los avatares solicitados."

View file

@ -7,62 +7,76 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-28 10:59+0200\n"
"POT-Creation-Date: 2016-09-14 16:37+0200\n"
"PO-Revision-Date: 2010-03-26 18:35+0100\n"
"Last-Translator: Mathieu Pillard <m.pillard@liberation.fr>\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"
#: forms.py:34
#: admin.py:26
#, fuzzy
#| msgid "Avatar for %s"
msgid "Avatar"
msgstr "Avatar pour %s"
#: forms.py:24 models.py:84 models.py:97
#, fuzzy
#| msgid "Default Avatar"
msgid "avatar"
msgstr "Avatar par défaut"
#: forms.py:37
#, 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 ""
"%(ext)s n'est pas une extension de fichier valide. Les extensions autorisées "
"sont: %(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 ""
"Le fichier est trop gros (%(size)s), la taille maximum autorisée est %"
"(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."
"Your file is too big (%(size)s), the maximum allowed size is "
"%(max_valid_size)s"
msgstr ""
"Vous avez déjà %(nb_avatars)d avatars, et le maximum autorisé est %"
"(nb_max_avatars)d."
"Le fichier est trop gros (%(size)s), la taille maximum autorisée est "
"%(max_valid_size)s"
#: forms.py:56 forms.py:67
msgid "Choices"
msgstr ""
#: models.py:75
#: forms.py:54
#, python-format
msgid "Avatar for %s"
msgid ""
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
"%(nb_max_avatars)d."
msgstr ""
"Vous avez déjà %(nb_avatars)d avatars, et le maximum autorisé est "
"%(nb_max_avatars)d."
#: forms.py:71 forms.py:84
msgid "Choices"
msgstr "Choix"
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "Avatar for %s"
msgid "avatars"
msgstr "Avatar pour %s"
#: views.py:73 views.py:95
msgid "Successfully uploaded a new avatar."
msgstr "Votre nouveau avatar a été uploadé avec succès."
#: views.py:132
msgid "Successfully updated your avatar."
msgstr "Votre avatar a été mis à jour avec succès."
#: views.py:166
msgid "Successfully deleted the requested avatars."
msgstr "Les avatars sélectionnés ont été effacés avec succès."
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "Votre avatar actuel:"
@ -89,8 +103,8 @@ msgid ""
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
"\">upload one</a> now."
msgstr ""
"Vous n'avez aucun avatar à effacer. Veuillez en <a href=\"%"
"(avatar_change_url)s\">ajouter</a> un maintenant."
"Vous n'avez aucun avatar à effacer. Veuillez en <a href="
"\"%(avatar_change_url)s\">ajouter</a> un maintenant."
#: templates/avatar/confirm_delete.html:14
msgid "Delete These"
@ -103,8 +117,9 @@ msgid ""
"\n"
"http://%(current_site)s%(avatar_url)s\n"
msgstr ""
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> a mis à jour son avatar <a "
"href=\"%(avatar_url)s\">%(avatar)s</a>."
"%(avatar_creator)s a mis à jour son avatar %(avatar)s\n"
"\n"
"http://%(current_site)s%(avatar_url)s\n"
#: templates/notification/avatar_friend_updated/notice.html:2
#, python-format
@ -122,16 +137,31 @@ msgid ""
"\n"
"http://%(current_site)s%(avatar_url)s\n"
msgstr ""
"Votre avatar a été mis à jour. %(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 "Vous avez mis à jour votre <a href=\"%(avatar_url)s\">%(avatar)s</a>."
#: templatetags/avatar_tags.py:45
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Avatar par défaut"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Votre nouveau avatar a été uploadé avec succès."
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Votre avatar a été mis à jour avec succès."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Les avatars sélectionnés ont été effacés avec succès."
#~ msgid "Avatar Updated"
#~ msgstr "Avatar mis à jour"

Binary file not shown.

View file

@ -0,0 +1,161 @@
# 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: 3.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-13 16:00+0200\n"
"PO-Revision-Date: 2013-08-27 00:21-0600\n"
"Last-Translator: Bruno Santeramo <bruno.santeramo@gmail.com>\n"
"Language-Team: it <bruno.santeramo@gmail.com>\n"
"Language: it\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: po2mo.net\n"
#: admin.py:26
msgid "Avatar"
msgstr "Avatar"
#: forms.py:24 models.py:84 models.py:97
msgid "avatar"
msgstr "avatar"
#: forms.py:37
#, python-format
msgid ""
"%(ext)s is an invalid file extension. Authorized extensions are : "
"%(valid_exts_list)s"
msgstr ""
"%(ext)s non è una estensione valida. Le estensioni accettate sono : "
"%(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 ""
"Il file è troppo grande (%(size)s), la massima dimensione consentita "
"è %(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 ""
"Hai già %(nb_avatars)d avatar, e il massimo numero consentito è "
"%(nb_max_avatars)d."
#: forms.py:71 forms.py:84
msgid "Choices"
msgstr "Opzioni"
#: models.py:77
msgid "user"
msgstr "utente"
#: models.py:80
msgid "primary"
msgstr "principale"
#: models.py:91
msgid "uploaded at"
msgstr "caricato su"
#: models.py:98
#, fuzzy
#| msgid "avatar"
msgid "avatars"
msgstr "avatar"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "Il tuo attuale avatar è:"
#: templates/avatar/add.html:8 templates/avatar/change.html:8
msgid "You haven't uploaded an avatar yet. Please upload one now."
msgstr "Non hai ancora caricato un avatar. Per favore, carica uno adesso."
#: templates/avatar/add.html:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "Carica una Nuova Immagine"
#: templates/avatar/change.html:14
msgid "Choose new Default"
msgstr "Scegli un nuovo predefinito"
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "Per favore, seleziona gli avatar che vuoi eliminare."
#: 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 ""
"Non hai avatar da eliminare. Per favore <a href=\"%(avatar_change_url)s"
"\">carica uno </a> adesso."
#: templates/avatar/confirm_delete.html:14
msgid "Delete These"
msgstr "Elimina Questi"
#: 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 ha aggiornato i suoi avatar %(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> ha aggiornato i suoi avatar <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 ""
"Il tuo avatar è stato aggiornato. %(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 "Hai aggiornato il tuo avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Avatar Predefinito"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Nuovo avatar caricato con successo"
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Il tuo avatar è stato aggiornato con successo."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Gli avatar selezionati sono stati eliminati con successo."

Binary file not shown.

View file

@ -0,0 +1,162 @@
# 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: 2016-09-14 16:37+0200\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"
"Language: \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:26
msgid "Avatar"
msgstr "プロフィール画像"
#: forms.py:24 models.py:84 models.py:97
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 "選択"
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "avatar"
msgid "avatars"
msgstr "プロフィール画像"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "現在のプロフィール画像:"
#: templates/avatar/add.html:8 templates/avatar/change.html:8
msgid "You haven't uploaded an avatar yet. Please upload one now."
msgstr "登録されているプロフィール画像はありません。アップロードしてください。"
#: templates/avatar/add.html:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "新しい画像のアップロード"
#: templates/avatar/change.html:14
msgid "Choose new Default"
msgstr "デフォルトの画像を選択"
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "削除したいプロフィール画像を選択してください。"
#: 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 ""
"削除できるプロフィール画像はありません。<a href=\"%(avatar_change_url)s\">新"
"規画像のアップロード</a>."
#: templates/avatar/confirm_delete.html:14
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:69
msgid "Default Avatar"
msgstr "デフォルトのプロフィール画像"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "新しいプロフィール画像をアップロードしました。"
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "プロフィール画像を更新しました。"
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "指定されたプロフィール画像を削除しました。"

Binary file not shown.

View file

@ -0,0 +1,160 @@
# 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: 2016-09-14 16:37+0200\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"
"Language: \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:26
msgid "Avatar"
msgstr "Profielfoto"
#: forms.py:24 models.py:84 models.py:97
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"
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "avatar"
msgid "avatars"
msgstr "profielfoto"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "De huidige profielfoto:"
#: templates/avatar/add.html:8 templates/avatar/change.html:8
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:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "Upload nieuw plaatje"
#: templates/avatar/change.html:14
msgid "Choose new Default"
msgstr "Kies nieuwe standaard"
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "Selecteer de te verwijderen de profielfoto's."
#: 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 ""
"Er zijn geen profielfoto's om te verwijderen. <a href=\"%(avatar_change_url)s"
"\">Upload</a> een nieuwe."
#: templates/avatar/confirm_delete.html:14
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:69
msgid "Default Avatar"
msgstr "Standaard profielfoto"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "De profielfoto is ververst."
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Profielfoto vernieuwd."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Profielfoto verwijderd."

Binary file not shown.

View file

@ -0,0 +1,164 @@
# 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 0.0.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-14 16:37+0200\n"
"PO-Revision-Date: 2015-07-19 15:46+0100\n"
"Last-Translator: Adam Dobrawy <naczelnik@jawnosc.tk>\n"
"Language-Team: Adam Dobrawy <naczelnik@jawnosc.tk>\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 1.5.4\n"
#: admin.py:26
msgid "Avatar"
msgstr "Avatar"
#: forms.py:24 models.py:84 models.py:97
msgid "avatar"
msgstr "avatar"
#: forms.py:37
#, python-format
msgid ""
"%(ext)s is an invalid file extension. Authorized extensions are : "
"%(valid_exts_list)s"
msgstr ""
"%(ext)s jest nieprawidłowym rozszerzeniem. Dozwolone rozszerzenia to: "
"%(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 ""
"Twój plik jest zbyt duży (%(size)s), maksymalny dopuszcalny rozmiar wynosi "
"%(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 ""
"Aktualnie masz %(nb_avatars)d avatarów, podczas gdy maksymalna dopuszczalna "
"liczba wynosi %(nb_max_avatars)d."
#: forms.py:71 forms.py:84
msgid "Choices"
msgstr "Opcje wyboru"
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "avatar"
msgid "avatars"
msgstr "avatar"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "Twój aktualny avatar"
#: templates/avatar/add.html:8 templates/avatar/change.html:8
msgid "You haven't uploaded an avatar yet. Please upload one now."
msgstr "Nie masz aktualnie żadnych avatarów. Prosimy wyślij teraz. "
#: templates/avatar/add.html:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "Wyślij nowy obraz"
#: templates/avatar/change.html:14
msgid "Choose new Default"
msgstr "Wybierz nowy domyślny"
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "Wybierz avatar, który chcesz usunąć."
#: 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 ""
"Nie masz avatarów do usunięcia. Prosimy <a href=\"%(avatar_change_url)s"
"\">dodaj nowy</a>."
#: templates/avatar/confirm_delete.html:14
msgid "Delete These"
msgstr "Usuń wybrane"
#: 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 zaktualizował / zaktualizowała avatar %(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> zaktualizował / "
"zaktualizował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 ""
"Twój avatar został zaktualizowany %(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 ""
"Zaktualizowałeś / zaktualizowałaś swój avatar <a href=\"%(avatar_url)s\">"
"%(avatar)s</a>."
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Domyślny avatar"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Pomyślnie wysłano nowy avatar."
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Pomyślnie zaktualizowano Twój avatar."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Pomyślnie usunięto wskazany avatar."

View file

@ -8,75 +8,93 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-28 10:59+0200\n"
"POT-Creation-Date: 2016-09-14 16:37+0200\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: forms.py:34
#, python-format
msgid ""
"%(ext)s is an invalid file extension. Authorized extensions are : %"
"(valid_exts_list)s"
msgstr ""
#: admin.py:26
#, fuzzy
#| msgid "Avatar for %s"
msgid "Avatar"
msgstr "Avatar para %s"
#: forms.py:38
#: forms.py:24 models.py:84 models.py:97
#, fuzzy
#| msgid "Default Avatar"
msgid "avatar"
msgstr "Foto de Perfil Padrão"
#: forms.py:37
#, python-format
msgid ""
"Your file is too big (%(size)s), the maximum allowed size is %"
"(max_valid_size)s"
"%(ext)s is an invalid file extension. Authorized extensions are : "
"%(valid_exts_list)s"
msgstr ""
"%(ext)s é uma extensão informada inválida. Os Formatos permitidos são : "
"%(valid_exts_list)s"
#: forms.py:44
#, python-format
msgid ""
"You already have %(nb_avatars)d avatars, and the maximum allowed is %"
"(nb_max_avatars)d."
"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:56 forms.py:67
msgid "Choices"
msgstr ""
#: models.py:75
#: forms.py:54
#, python-format
msgid "Avatar for %s"
msgid ""
"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:71 forms.py:84
msgid "Choices"
msgstr "Opções"
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "Avatar for %s"
msgid "avatars"
msgstr "Avatar para %s"
#: views.py:73 views.py:95
msgid "Successfully uploaded a new avatar."
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."
#: views.py:166
msgid "Successfully deleted the requested avatars."
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
@ -84,10 +102,12 @@ msgid ""
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
"\">upload one</a> now."
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
@ -96,6 +116,8 @@ msgid ""
"\n"
"http://%(current_site)s%(avatar_url)s\n"
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>."
@ -115,6 +137,9 @@ msgid ""
"\n"
"http://%(current_site)s%(avatar_url)s\n"
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
@ -123,15 +148,27 @@ msgstr ""
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> atualizou a foto de perfil "
"<a href=\"%(avatar_url)s\">%(avatar)s</a>."
#: templatetags/avatar_tags.py:45
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Foto de Perfil Padrão"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Nova foto de perfil enviada com sucesso."
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Sua foto foi atualizada com sucesso."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "As fotos de perfil selecionadas foram excluídas com sucesso."
#~ msgid "Avatar Updated"
#~ 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"

View file

@ -7,10 +7,11 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-17 00:31+0400\n"
"POT-Creation-Date: 2016-09-14 16:37+0200\n"
"PO-Revision-Date: 2012-03-17 00:31+0400\n"
"Last-Translator: frost-nzcr4 <frost.nzcr4@jagmort.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"
@ -19,50 +20,74 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Poedit-SourceCharset: utf-8\n"
#: forms.py:33
#, python-format
msgid "%(ext)s is an invalid file extension. Authorized extensions are : %(valid_exts_list)s"
msgstr "%(ext)s запрещённое расширение. Разрешённые расширения: %(valid_exts_list)s"
#: admin.py:26
#, fuzzy
#| msgid "Avatar for %s"
msgid "Avatar"
msgstr "Аватар для %s"
#: forms.py:24 models.py:84 models.py:97
#, fuzzy
#| msgid "Default Avatar"
msgid "avatar"
msgstr "Аватар по умолчанию"
#: forms.py:37
#, 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"
msgid ""
"%(ext)s is an invalid file extension. Authorized extensions are : "
"%(valid_exts_list)s"
msgstr ""
"%(ext)s запрещённое расширение. Разрешённые расширения: %(valid_exts_list)s"
#: forms.py:43
#: forms.py:44
#, 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."
msgid ""
"Your file is too big (%(size)s), the maximum allowed size is "
"%(max_valid_size)s"
msgstr ""
"Файл слишком большой (%(size)s), максимальный допустимый размер "
"%(max_valid_size)s"
#: models.py:72
#: forms.py:54
#, python-format
msgid "Avatar for %s"
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 ""
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "Avatar for %s"
msgid "avatars"
msgstr "Аватар для %s"
#: views.py:90
msgid "Successfully uploaded a new avatar."
msgstr "Новый аватар загружен."
#: views.py:128
msgid "Successfully updated your avatar."
msgstr "Аватар обновлён."
#: views.py:166
msgid "Successfully deleted the requested avatars."
msgstr "Выбранные аватары удалены."
#: templates/avatar/add.html:5
#: templates/avatar/change.html:5
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "Ваш аватар:"
#: templates/avatar/add.html:8
#: templates/avatar/change.html:8
#: templates/avatar/add.html:8 templates/avatar/change.html:8
msgid "You haven't uploaded an avatar yet. Please upload one now."
msgstr "Вы ещё не загружали аватар. Пожалуйста, загрузите его."
#: templates/avatar/add.html:12
#: templates/avatar/change.html:19
#: templates/avatar/add.html:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "Загрузить новое изображение"
@ -76,24 +101,64 @@ msgstr "Выберите аватары, которые собираетесь
#: 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 "У вас нет аватаров. <a href=\"%(avatar_change_url)s\">Загрузите</a> его."
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:14
msgid "Delete These"
msgstr "Удалить эти"
#: templates/notification/avatar_friend_updated/full.txt:1
#, fuzzy, python-format
#| msgid ""
#| "<a href=\"%(user_url)s\">%(avatar_creator)s</a> has updated their avatar "
#| "<a href=\"%(avatar_url)s\">%(avatar)s</a>."
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>."
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 ""
#: 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:40
#: templatetags/avatar_tags.py:69
msgid "Default Avatar"
msgstr "Аватар по умолчанию"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "Новый аватар загружен."
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "Аватар обновлён."
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "Выбранные аватары удалены."

Binary file not shown.

View file

@ -0,0 +1,154 @@
# 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: 2016-09-14 16:37+0200\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"
"Language: \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:26
msgid "Avatar"
msgstr "头像"
#: forms.py:24 models.py:84 models.py:97
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 "选项"
#: models.py:77
msgid "user"
msgstr ""
#: models.py:80
msgid "primary"
msgstr ""
#: models.py:91
msgid "uploaded at"
msgstr ""
#: models.py:98
#, fuzzy
#| msgid "avatar"
msgid "avatars"
msgstr "头像"
#: templates/avatar/add.html:5 templates/avatar/change.html:5
msgid "Your current avatar: "
msgstr "您当前的头像:"
#: templates/avatar/add.html:8 templates/avatar/change.html:8
msgid "You haven't uploaded an avatar yet. Please upload one now."
msgstr "您还没有上传任何头像,请现在上传一个吧。"
#: templates/avatar/add.html:12 templates/avatar/change.html:19
msgid "Upload New Image"
msgstr "上传新照片"
#: templates/avatar/change.html:14
msgid "Choose new Default"
msgstr "选择默认"
#: templates/avatar/confirm_delete.html:5
msgid "Please select the avatars that you would like to delete."
msgstr "选择要删除的头像。"
#: 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 ""
"没有头像可以删除. 请 <a href=\"%(avatar_change_url)s\">上传一个新头像</a>。"
#: templates/avatar/confirm_delete.html:14
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:69
msgid "Default Avatar"
msgstr "默认头像"
#: views.py:73
msgid "Successfully uploaded a new avatar."
msgstr "成功上传头像。"
#: views.py:111
msgid "Successfully updated your avatar."
msgstr "更新头像成功。"
#: views.py:150
msgid "Successfully deleted the requested avatars."
msgstr "成功删除头像。"

View file

@ -1,15 +1,17 @@
from django.core.management.base import NoArgsCommand
from django.core.management.base import BaseCommand
from avatar.conf import settings
from avatar.models import Avatar
class Command(NoArgsCommand):
class Command(BaseCommand):
help = ("Regenerates avatar thumbnails for the sizes specified in "
"settings.AVATAR_AUTO_GENERATE_SIZES.")
def handle_noargs(self, **options):
def handle(self, *args, **options):
for avatar in Avatar.objects.all():
for size in settings.AVATAR_AUTO_GENERATE_SIZES:
print("Rebuilding Avatar id=%s at size %s." % (avatar.id, size))
if options['verbosity'] != 0:
print("Rebuilding Avatar id=%s at size %s." % (avatar.id, size))
avatar.create_thumbnail(size)

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import avatar.models
import django.core.files.storage
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Avatar',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('primary', models.BooleanField(default=False)),
('avatar', models.ImageField(storage=django.core.files.storage.FileSystemStorage(), max_length=1024, upload_to=avatar.models.avatar_file_path, blank=True)),
('date_uploaded', models.DateTimeField(default=django.utils.timezone.now)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-16 08:50
from __future__ import unicode_literals
import avatar.models
from django.conf import settings
import django.core.files.storage
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('avatar', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='avatar',
options={'verbose_name': 'avatar', 'verbose_name_plural': 'avatars'},
),
migrations.AlterField(
model_name='avatar',
name='avatar',
field=models.ImageField(blank=True, max_length=1024, storage=django.core.files.storage.FileSystemStorage(), upload_to=avatar.models.avatar_path_handler, verbose_name='avatar'),
),
migrations.AlterField(
model_name='avatar',
name='date_uploaded',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='uploaded at'),
),
migrations.AlterField(
model_name='avatar',
name='primary',
field=models.BooleanField(default=False, verbose_name='primary'),
),
migrations.AlterField(
model_name='avatar',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-27 13:45
from __future__ import unicode_literals
import avatar.models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('avatar', '0002_add_verbose_names_to_avatar_fields'),
]
operations = [
migrations.AlterField(
model_name='avatar',
name='avatar',
field=avatar.models.AvatarField(),
),
]

View file

View file

@ -1,3 +1,4 @@
import binascii
import datetime
import os
import hashlib
@ -7,12 +8,14 @@ from django.db import models
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.storage import get_storage_class
from django.utils.module_loading import import_string
from django.utils.translation import ugettext as _
from django.utils.encoding import force_text
from django.utils import six
from django.db.models import signals
from avatar.conf import settings
from avatar.util import get_username, force_bytes, invalidate_cache
from avatar.utils import get_username, force_bytes, invalidate_cache
try:
from django.utils.timezone import now
@ -23,13 +26,15 @@ except ImportError:
avatar_storage = get_storage_class(settings.AVATAR_STORAGE)()
def avatar_file_path(instance=None, filename=None, size=None, ext=None):
def avatar_path_handler(instance=None, filename=None, size=None, ext=None):
tmppath = [settings.AVATAR_STORAGE_DIR]
if settings.AVATAR_HASH_USERDIRNAMES:
tmp = hashlib.md5(get_username(instance.user)).hexdigest()
tmppath.extend([tmp[0], tmp[1], get_username(instance.user)])
else:
tmp = hashlib.md5(force_bytes(get_username(instance.user))).hexdigest()
tmppath.extend(tmp[0:2])
if settings.AVATAR_EXPOSE_USERNAMES:
tmppath.append(get_username(instance.user))
else:
tmppath.append(force_text(instance.user.pk))
if not filename:
# Filename already stored in database
filename = instance.avatar.name
@ -44,7 +49,10 @@ def avatar_file_path(instance=None, filename=None, size=None, ext=None):
# File doesn't exist yet
if settings.AVATAR_HASH_FILENAMES:
(root, ext) = os.path.splitext(filename)
filename = hashlib.md5(force_bytes(filename)).hexdigest()
if settings.AVATAR_RANDOMIZE_HASHES:
filename = binascii.hexlify(os.urandom(16)).decode('ascii')
else:
filename = hashlib.md5(force_bytes(filename)).hexdigest()
filename = filename + ext
if size:
tmppath.extend(['resized', str(size)])
@ -52,6 +60,9 @@ def avatar_file_path(instance=None, filename=None, size=None, ext=None):
return os.path.join(*tmppath)
avatar_file_path = import_string(settings.AVATAR_PATH_HANDLER)
def find_extension(format):
format = format.lower()
@ -61,14 +72,42 @@ def find_extension(format):
return format
class AvatarField(models.ImageField):
def __init__(self, *args, **kwargs):
super(AvatarField, self).__init__(*args, **kwargs)
self.max_length = 1024
self.upload_to = avatar_file_path
self.storage = avatar_storage
self.blank = True
def deconstruct(self):
name, path, args, kwargs = super(models.ImageField, self).deconstruct()
return name, path, (), {}
class Avatar(models.Model):
user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL', 'auth.User'))
primary = models.BooleanField(default=False)
avatar = models.ImageField(max_length=1024,
upload_to=avatar_file_path,
storage=avatar_storage,
blank=True)
date_uploaded = models.DateTimeField(default=now)
user = models.ForeignKey(
getattr(settings, 'AUTH_USER_MODEL', 'auth.User'),
verbose_name=_("user"),
)
primary = models.BooleanField(
verbose_name=_("primary"),
default=False,
)
avatar = AvatarField(
verbose_name=_("avatar")
)
date_uploaded = models.DateTimeField(
verbose_name=_("uploaded at"),
default=now,
)
class Meta:
app_label = 'avatar'
verbose_name = _('avatar')
verbose_name_plural = _('avatars')
def __unicode__(self):
return _(six.u('Avatar for %s')) % self.user
@ -103,7 +142,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()

84
avatar/providers.py Normal file
View file

@ -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
)

View file

@ -1,6 +1,5 @@
{% extends "avatar/base.html" %}
{% load i18n avatar_tags %}
{% load url from future %}
{% block content %}
<p>{% trans "Your current avatar: " %}</p>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title><!-- Insert your title here --></title>
</head>
<body>
<!-- Insert your content here -->
</body>
</html>

View file

@ -1 +1 @@
<img src="{{ url }}" alt="{{ alt }}" width="{{ size }}" height="{{ size }}" />
<img src="{{ url }}" width="{{ size }}" height="{{ size }}" {% for key, value in kwargs.items %}{{key}}="{{value}}" {% endfor %}/>

View file

@ -1,6 +1,5 @@
{% extends "avatar/base.html" %}
{% load i18n avatar_tags %}
{% load url from future %}
{% block content %}
<p>{% trans "Your current avatar: " %}</p>

View file

@ -1,6 +1,5 @@
{% extends "avatar/base.html" %}
{% load i18n %}
{% load url from future %}
{% block content %}
<p>{% trans "Please select the avatars that you would like to delete." %}</p>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title><!-- Insert your title here --></title>
</head>
<body>
<!-- Insert your content here -->
</body>
</html>

View file

@ -1,2 +1,2 @@
{% load i18n %}{% load url from future %}{% url 'profile_detail' username=user.username as user_url %}{# TODO: support custom user models via get_username; actually, is this template even used anymore? #}
{% load i18n %}{% url 'profile_detail' username=user.username as user_url %}{# TODO: support custom user models via get_username; actually, is this template even used anymore? #}
{% blocktrans with user as avatar_creator and avatar.get_absolute_url as avatar_url %}<a href="{{ user_url }}">{{ avatar_creator }}</a> has updated their avatar <a href="{{ avatar_url }}">{{ avatar }}</a>.{% endblocktrans %}

View file

@ -1,21 +1,19 @@
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
from django.utils import six
from django.utils.translation import ugettext as _
from django.utils.module_loading import import_string
from avatar.conf import settings
from avatar.util 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()
@ -23,19 +21,11 @@ register = template.Library()
@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
path = "%s/?%s" % (hashlib.md5(force_bytes(user.email)).hexdigest(),
urlencode(params))
return urljoin(settings.AVATAR_GRAVATAR_BASE_URL, path)
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()
@ -52,12 +42,14 @@ def avatar(user, size=settings.AVATAR_DEFAULT_SIZE, **kwargs):
else:
alt = six.text_type(user)
url = avatar_url(user, size)
context = dict(kwargs, **{
kwargs.update({'alt': alt})
context = {
'user': user,
'url': url,
'alt': alt,
'size': size,
})
'kwargs': kwargs,
}
return render_to_string('avatar/avatar_tag.html', context)

View file

@ -1,14 +1,12 @@
try:
from django.conf.urls import patterns, url
except ImportError:
# Django < 1.4
from django.conf.urls.defaults import patterns, url
from django.conf.urls import url
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'^list/(?P<username>[\+\w\@\.]+)/$', 'avatar_gallery', name='avatar_gallery'),
url(r'^list/(?P<username>[\+\w\@\.]+)/(?P<id>[\d]+)/$', 'avatar', name='avatar'),
)
from avatar import views
urlpatterns = [
url(r'^add/$', views.add, name='avatar_add'),
url(r'^change/$', views.change, name='avatar_change'),
url(r'^delete/$', views.delete, name='avatar_delete'),
url(r'^render_primary/(?P<user>[\w\d\@\.\-_]+)/(?P<size>[\d]+)/$',
views.render_primary,
name='avatar_render_primary'),
]

View file

@ -9,17 +9,7 @@ try:
except ImportError:
force_bytes = str
try:
from django.contrib.auth import get_user_model
except ImportError:
from django.contrib.auth.models import User
def get_user_model():
return User
custom_user_model = False
else:
custom_user_model = True
from django.contrib.auth import get_user_model
from avatar.conf import settings
@ -37,10 +27,7 @@ def get_username(user):
def get_user(username):
""" Return user from a username/ish identifier """
if custom_user_model:
return get_user_model().objects.get_by_natural_key(username)
else:
return get_user_model().objects.get(username=username)
return get_user_model().objects.get_by_natural_key(username)
def get_cache_key(user_or_username, size, prefix):
@ -64,14 +51,19 @@ def cache_result(default_size=settings.AVATAR_DEFAULT_SIZE):
Decorator to cache the result of functions that take a ``user`` and a
``size`` value.
"""
if not settings.AVATAR_CACHE_ENABLED:
def decorator(func):
return func
return decorator
def decorator(func):
def cached_func(user, size=None):
def cached_func(user, size=None, **kwargs):
prefix = func.__name__
cached_funcs.add(prefix)
key = get_cache_key(user, size or default_size, prefix=prefix)
result = cache.get(key)
if result is None:
result = func(user, size or default_size)
result = func(user, size or default_size, **kwargs)
cache_set(key, result)
return result
return cached_func

View file

@ -1,4 +1,3 @@
from django.http import Http404
from django.shortcuts import render, redirect
from django.utils import six
from django.utils.translation import ugettext as _
@ -10,8 +9,8 @@ from avatar.conf import settings
from avatar.forms import PrimaryAvatarForm, DeleteAvatarForm, UploadAvatarForm
from avatar.models import Avatar
from avatar.signals import avatar_updated
from avatar.util import (get_primary_avatar, get_default_avatar_url,
get_user_model, get_user)
from avatar.utils import (get_primary_avatar, get_default_avatar_url,
invalidate_cache)
def _get_next(request):
@ -81,7 +80,8 @@ def add(request, extra_context=None, next_override=None,
'next': next_override or _get_next(request),
}
context.update(extra_context)
return render(request, 'avatar/add.html', context)
template_name = settings.AVATAR_ADD_TEMPLATE or 'avatar/add.html'
return render(request, template_name, context)
@login_required
@ -107,6 +107,7 @@ def change(request, extra_context=None, next_override=None,
avatar.primary = True
avatar.save()
updated = True
invalidate_cache(request.user)
messages.success(request, _("Successfully updated your avatar."))
if updated:
avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar)
@ -120,7 +121,8 @@ def change(request, extra_context=None, next_override=None,
'next': next_override or _get_next(request)
}
context.update(extra_context)
return render(request, 'avatar/change.html', context)
template_name = settings.AVATAR_CHANGE_TEMPLATE or 'avatar/change.html'
return render(request, template_name, context)
@login_required
@ -155,69 +157,10 @@ def delete(request, extra_context=None, next_override=None, *args, **kwargs):
'next': next_override or _get_next(request),
}
context.update(extra_context)
return render(request, 'avatar/confirm_delete.html', context)
def avatar_gallery(request, username, template_name="avatar/gallery.html"):
try:
user = get_user(username)
except get_user_model().DoesNotExist:
raise Http404
context = {
"other_user": user,
"avatars": user.avatar_set.all(),
}
template_name = settings.AVATAR_DELETE_TEMPLATE or 'avatar/confirm_delete.html'
return render(request, template_name, context)
def avatar(request, username, id, template_name="avatar/avatar.html"):
try:
user = get_user(username)
except get_user_model().DoesNotExist:
raise Http404
avatars = user.avatar_set.order_by("-date_uploaded")
index = None
avatar = None
if avatars:
avatar = avatars.get(pk=id)
if not avatar:
return Http404
index = avatars.filter(date_uploaded__gt=avatar.date_uploaded).count()
count = avatars.count()
if index == 0:
prev = avatars.reverse()[0]
if count <= 1:
next = avatars[0]
else:
next = avatars[1]
else:
prev = avatars[index - 1]
if (index + 1) >= count:
next = avatars[0]
prev_index = index - 1
if prev_index < 0:
prev_index = 0
prev = avatars[prev_index]
else:
next = avatars[index + 1]
return render(request, template_name, {
"other_user": user,
"avatar": avatar,
"index": index + 1,
"avatars": avatars,
"next": next,
"prev": prev,
"count": count,
})
def render_primary(request, user=None, size=settings.AVATAR_DEFAULT_SIZE):
size = int(size)
avatar = get_primary_avatar(user, size=size)

View file

@ -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
@ -32,43 +33,51 @@ 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
will look something like::
urlpatterns = patterns('',
# ...
(r'^avatar/', include('avatar.urls')),
)
2. Migrate your database::
3. Somewhere in your template navigation scheme, link to the change avatar
python manage.py migrate
3. Add the avatar urls to the end of your root urlconf. Your urlconf
will look something like::
urlpatterns = [
# ...
url(r'^avatar/', include('avatar.urls')),
]
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 %}
Example for customize the attribute of the HTML ``img`` tag::
{% avatar user 65 class="img-circle img-responsive" id="user_avatar" %}
Template tags and filter
------------------------
To begin using these template tags, you must first load the tags into the
template rendering system:
template rendering system::
{% load avatar_tags %}
@ -76,10 +85,11 @@ template rendering system:
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] %}``
``{% avatar user [size in pixels] **kwargs %}``
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.
username. The (key, value) pairs in kwargs will be added to ``img`` tag
as its attributes.
``{% render_avatar avatar [size in pixels] %}``
Given an actual ``avatar.models.Avatar`` object instance, renders an HTML
@ -94,36 +104,142 @@ 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_RESIZE_METHOD
.. py:data:: AVATAR_CACHE_ENABLED
Set to ``False`` if you completely disable avatar caching. Defaults to ``True``.
.. py:data:: AVATAR_DEFAULT_URL
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.
.. py:data:: 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``.
.. py:data:: 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 you included
:py:class:`~avatar.providers.FacebookAvatarProvider` in :py:data:`AVATAR_PROVIDERS`.
.. py:data:: 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 <https://en.gravatar.com/site/implement/images
/#default-image>`_. 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
<https://en.gravatar.com/site/implement/images/#force-default>`_. 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
File size limit for avatar upload. Default is ``1024 * 1024`` (1 MB).
gravatar in ``user.gravatar``. Defaults to ``email``.
.. py:data:: AVATAR_MAX_AVATARS_PER_USER
The maximum number of avatars each user can have. Default is ``42``.
.. py:data:: AVATAR_PATH_HANDLER
Path to a method for avatar file path handling. Default is
``avatar.models.avatar_path_handler``.
.. 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.PrimaryAvatarProvider
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.
of the file name. Defaults to ``avatars``.
PIL. Defaults to ``Image.ANTIALIAS``.
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.
.. py:data:: AVATAR_CLEANUP_DELETED
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.
``True`` if the avatar image files should be deleted when an avatar is
deleted from the database. Defaults to ``False``.
.. 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
Path to the Django template to use for changing a user's avatar. Defaults to ``avatar/change.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 ``AUTO_GENERATE_AVATAR_SIZES``
setting.
the avatars for the pixel sizes specified in the
:py:data:`AVATAR_AUTO_GENERATE_SIZES` setting.
.. _pip: http://www.pip-installer.org/

View file

@ -28,20 +28,26 @@ setup(
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'Framework :: Django',
'Framework :: Django :: 1.9',
'Framework :: Django :: 1.10',
'Framework :: Django :: 1.11',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
keywords='avatar, django',
author='Eric Florenzano',
author_email='floguy@gmail.com',
maintainer='Jannis Leidel',
maintainer_email='jannis@leidel.info',
url='http://github.com/jezdez/django-avatar/',
maintainer='Grant McConnaughey',
maintainer_email='grantmcconnaughey@gmail.com',
url='http://github.com/grantmcconnaughey/django-avatar/',
license='BSD',
packages=find_packages(exclude=['tests']),
package_data={

26
test_proj/manage.py Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_proj.settings")
# Add the django-avatar directory to the Python path. That way the
# avatar module can be imported.
sys.path.append('..')
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)

View file

View file

@ -0,0 +1,106 @@
"""
Django settings for test_proj project.
Generated by 'django-admin startproject' using Django 1.10.1.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '0o$jym8^hgw%vwx9hy%@ncr!29n7gik30(ln$pd$!3*4zu+9dv'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'avatar',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'test_proj.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'test_proj.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

View file

@ -0,0 +1,17 @@
from django.conf import settings
from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^avatar/', include('avatar.urls')),
]
if settings.DEBUG:
# static files (images, css, javascript, etc.)
urlpatterns += [
url(r'^media/(?P<path>.*)$', serve, {
'document_root': settings.MEDIA_ROOT})
]

View file

@ -0,0 +1,16 @@
"""
WSGI config for test_proj project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_proj.settings")
application = get_wsgi_application()

BIN
tests/data/django.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,3 +1,3 @@
flake8
coverage
coverage==4.2
django-discover-runner

View file

@ -1,5 +1,9 @@
import os
import django
VERSION = django.VERSION
SETTINGS_DIR = os.path.dirname(__file__)
DATABASE_ENGINE = 'sqlite3'
DATABASES = {
@ -14,10 +18,32 @@ INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.comments',
'avatar',
]
MIDDLEWARE_CLASSES = (
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
)
if VERSION[0] == 1 and VERSION[1] < 8:
TEMPLATE_DIRS = (
os.path.join(SETTINGS_DIR, 'templates'),
)
else:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': [
os.path.join(SETTINGS_DIR, 'templates')
]
}
]
ROOT_URLCONF = 'tests.urls'
SITE_ID = 1

View file

@ -0,0 +1 @@
ALTERNATE ADD TEMPLATE

View file

@ -0,0 +1 @@
ALTERNATE CHANGE TEMPLATE

View file

@ -0,0 +1 @@
ALTERNATE DELETE TEMPLATE

View file

@ -1,11 +1,15 @@
import os.path
from django.contrib.admin.sites import AdminSite
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from avatar.admin import AvatarAdmin
from avatar.conf import settings
from avatar.util import get_primary_avatar, get_user_model
from avatar.utils import get_primary_avatar, get_user_model
from avatar.models import Avatar
from avatar.templatetags import avatar_tags
from PIL import Image
@ -18,50 +22,65 @@ def upload_helper(o, filename):
return response
class AvatarUploadTests(TestCase):
class AvatarTests(TestCase):
def setUp(self):
self.testdatapath = os.path.join(os.path.dirname(__file__), "data")
self.user = get_user_model().objects.create_user('test', 'lennon@thebeatles.com', 'testpassword')
self.user.save()
self.client.login(username='test', password='testpassword')
self.site = AdminSite()
Image.init()
def testNonImageUpload(self):
def test_admin_get_avatar_returns_different_image_tags(self):
self.test_normal_image_upload()
self.test_normal_image_upload()
primary = Avatar.objects.get(primary=True)
old = Avatar.objects.get(primary=False)
aa = AvatarAdmin(Avatar, self.site)
primary_link = aa.get_avatar(primary)
old_link = aa.get_avatar(old)
self.assertNotEqual(primary_link, old_link)
def test_non_image_upload(self):
response = upload_helper(self, "nonimagefile")
self.assertEqual(response.status_code, 200)
self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
def testNormalImageUpload(self):
def test_normal_image_upload(self):
response = upload_helper(self, "test.png")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 1)
self.assertEqual(response.context['upload_avatar_form'].errors, {})
avatar = get_primary_avatar(self.user)
self.assertNotEqual(avatar, None)
self.assertIsNotNone(avatar)
self.assertEqual(avatar.user, self.user)
self.assertTrue(avatar.primary)
def testImageWithoutExtension(self):
def test_image_without_wrong_extension(self):
# use with AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
response = upload_helper(self, "imagefilewithoutext")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
def testImageWithWrongExtension(self):
def test_image_with_wrong_extension(self):
# use with AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
response = upload_helper(self, "imagefilewithwrongext.ogg")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
def testImageTooBig(self):
def test_image_too_big(self):
# use with AVATAR_MAX_SIZE = 1024 * 1024
response = upload_helper(self, "testbig.png")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.redirect_chain), 0) # Redirect only if it worked
self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
def testDefaultUrl(self):
def test_default_url(self):
response = self.client.get(reverse('avatar_render_primary', kwargs={
'user': self.user.username,
'size': 80,
@ -73,18 +92,18 @@ class AvatarUploadTests(TestCase):
self.assertTrue(base_url in loc)
self.assertTrue(loc.endswith(settings.AVATAR_DEFAULT_URL))
def testNonExistingUser(self):
def test_non_existing_user(self):
a = get_primary_avatar("nonexistinguser")
self.assertEqual(a, None)
def testThereCanBeOnlyOnePrimaryAvatar(self):
def test_there_can_be_only_one_primary_avatar(self):
for i in range(1, 10):
self.testNormalImageUpload()
self.test_normal_image_upload()
count = Avatar.objects.filter(user=self.user, primary=True).count()
self.assertEqual(count, 1)
def testDeleteAvatar(self):
self.testNormalImageUpload()
def test_delete_avatar(self):
self.test_normal_image_upload()
avatar = Avatar.objects.filter(user=self.user)
self.assertEqual(len(avatar), 1)
response = self.client.post(reverse('avatar_delete'), {
@ -95,8 +114,8 @@ class AvatarUploadTests(TestCase):
count = Avatar.objects.filter(user=self.user).count()
self.assertEqual(count, 0)
def testDeletePrimaryAvatarAndNewPrimary(self):
self.testThereCanBeOnlyOnePrimaryAvatar()
def test_delete_primary_avatar_and_new_primary(self):
self.test_there_can_be_only_one_primary_avatar()
primary = get_primary_avatar(self.user)
oid = primary.id
self.client.post(reverse('avatar_delete'), {
@ -108,9 +127,30 @@ class AvatarUploadTests(TestCase):
avatars = Avatar.objects.filter(user=self.user)
self.assertEqual(avatars[0].id, primaries[0].id)
def testTooManyAvatars(self):
def test_change_avatar_get(self):
self.test_normal_image_upload()
response = self.client.get(reverse('avatar_change'))
self.assertEqual(response.status_code, 200)
self.assertIsNotNone(response.context['avatar'])
def test_change_avatar_post_updates_primary_avatar(self):
self.test_there_can_be_only_one_primary_avatar()
old_primary = Avatar.objects.get(user=self.user, primary=True)
choice = Avatar.objects.filter(user=self.user, primary=False)[0]
response = self.client.post(reverse('avatar_change'), {
'choice': choice.pk,
})
self.assertEqual(response.status_code, 302)
new_primary = Avatar.objects.get(user=self.user, primary=True)
self.assertEqual(new_primary.pk, choice.pk)
# Avatar with old primary pk exists but it is not primary anymore
self.assertTrue(Avatar.objects.filter(user=self.user, pk=old_primary.pk, primary=False).exists())
def test_too_many_avatars(self):
for i in range(0, settings.AVATAR_MAX_AVATARS_PER_USER):
self.testNormalImageUpload()
self.test_normal_image_upload()
count_before = Avatar.objects.filter(user=self.user).count()
response = upload_helper(self, "test.png")
count_after = Avatar.objects.filter(user=self.user).count()
@ -119,6 +159,98 @@ class AvatarUploadTests(TestCase):
self.assertNotEqual(response.context['upload_avatar_form'].errors, {})
self.assertEqual(count_before, count_after)
@override_settings(AVATAR_THUMB_FORMAT='png')
def test_automatic_thumbnail_creation_RGBA(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 test_automatic_thumbnail_creation_CMYK(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')
def test_has_avatar_False_if_no_avatar(self):
self.assertFalse(avatar_tags.has_avatar(self.user))
def test_has_avatar_False_if_not_user_model(self):
self.assertFalse(avatar_tags.has_avatar("Look, I'm a string"))
def test_has_avatar_True(self):
upload_helper(self, "test.png")
self.assertTrue(avatar_tags.has_avatar(self.user))
def test_avatar_tag_works_with_username(self):
upload_helper(self, "test.png")
avatar = get_primary_avatar(self.user)
result = avatar_tags.avatar(self.user.username)
self.assertIn('<img src="{}"'.format(avatar.avatar_url(80)), result)
self.assertIn('width="80" height="80" alt="test" />', result)
def test_avatar_tag_works_with_user(self):
upload_helper(self, "test.png")
avatar = get_primary_avatar(self.user)
result = avatar_tags.avatar(self.user)
self.assertIn('<img src="{}"'.format(avatar.avatar_url(80)), result)
self.assertIn('width="80" height="80" alt="test" />', result)
def test_avatar_tag_works_with_custom_size(self):
upload_helper(self, "test.png")
avatar = get_primary_avatar(self.user)
result = avatar_tags.avatar(self.user, 100)
self.assertIn('<img src="{}"'.format(avatar.avatar_url(100)), result)
self.assertIn('width="100" height="100" alt="test" />', result)
def test_avatar_tag_works_with_kwargs(self):
upload_helper(self, "test.png")
avatar = get_primary_avatar(self.user)
result = avatar_tags.avatar(self.user, title="Avatar")
html = '<img src="{}" width="80" height="80" alt="test" title="Avatar" />'.format(avatar.avatar_url(80))
self.assertInHTML(html, result)
def test_default_add_template(self):
response = self.client.get('/avatar/add/')
self.assertContains(response, 'Upload New Image')
self.assertNotContains(response, 'ALTERNATE ADD TEMPLATE')
@override_settings(AVATAR_ADD_TEMPLATE='alt/add.html')
def test_custom_add_template(self):
response = self.client.get('/avatar/add/')
self.assertNotContains(response, 'Upload New Image')
self.assertContains(response, 'ALTERNATE ADD TEMPLATE')
def test_default_change_template(self):
response = self.client.get('/avatar/change/')
self.assertContains(response, 'Upload New Image')
self.assertNotContains(response, 'ALTERNATE CHANGE TEMPLATE')
@override_settings(AVATAR_CHANGE_TEMPLATE='alt/change.html')
def test_custom_change_template(self):
response = self.client.get('/avatar/change/')
self.assertNotContains(response, 'Upload New Image')
self.assertContains(response, 'ALTERNATE CHANGE TEMPLATE')
def test_default_delete_template(self):
response = self.client.get('/avatar/delete/')
self.assertContains(response, 'like to delete.')
self.assertNotContains(response, 'ALTERNATE DELETE TEMPLATE')
@override_settings(AVATAR_DELETE_TEMPLATE='alt/delete.html')
def test_custom_delete_template(self):
response = self.client.get('/avatar/delete/')
self.assertNotContains(response, 'like to delete.')
self.assertContains(response, 'ALTERNATE DELETE TEMPLATE')
# def testAvatarOrder
# def testReplaceAvatarWhenMaxIsOne
# def testHashFileName

View file

@ -1,9 +1,6 @@
try:
from django.conf.urls import patterns, include
except ImportError:
from django.conf.urls.defaults import patterns, include
from django.conf.urls import include, url
urlpatterns = patterns('',
(r'^avatar/', include('avatar.urls')),
)
urlpatterns = [
url(r'^avatar/', include('avatar.urls')),
]