Merge branch 'feature/avatar' into master2
0
avatar/__init__.py
Normal file
4
avatar/admin.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from django.contrib import admin
|
||||
from avatar.models import Avatar
|
||||
|
||||
admin.site.register(Avatar)
|
69
avatar/forms.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
|
||||
from django import forms
|
||||
from django.forms import widgets
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
|
||||
from avatar.models import Avatar
|
||||
from avatar.settings import (AVATAR_MAX_AVATARS_PER_USER, AVATAR_MAX_SIZE,
|
||||
AVATAR_ALLOWED_FILE_EXTS, AVATAR_DEFAULT_SIZE)
|
||||
|
||||
|
||||
def avatar_img(avatar, size):
|
||||
if not avatar.thumbnail_exists(size):
|
||||
avatar.create_thumbnail(size)
|
||||
return mark_safe("""<img src="%s" alt="%s" width="%s" height="%s" />""" %
|
||||
(avatar.avatar_url(size), unicode(avatar), size, size))
|
||||
|
||||
class UploadAvatarForm(forms.Form):
|
||||
|
||||
avatar = forms.ImageField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.emailuser = kwargs.pop('user').email
|
||||
super(UploadAvatarForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_avatar(self):
|
||||
data = self.cleaned_data['avatar']
|
||||
if AVATAR_ALLOWED_FILE_EXTS:
|
||||
(root, ext) = os.path.splitext(data.name.lower())
|
||||
if ext not in AVATAR_ALLOWED_FILE_EXTS:
|
||||
raise forms.ValidationError(
|
||||
_(u"%(ext)s is an invalid file extension. Authorized extensions are : %(valid_exts_list)s") %
|
||||
{ 'ext' : ext, 'valid_exts_list' : ", ".join(AVATAR_ALLOWED_FILE_EXTS) })
|
||||
if data.size > AVATAR_MAX_SIZE:
|
||||
raise forms.ValidationError(
|
||||
_(u"Your file is too big (%(size)s), the maximum allowed size is %(max_valid_size)s") %
|
||||
{ 'size' : filesizeformat(data.size), 'max_valid_size' : filesizeformat(AVATAR_MAX_SIZE)} )
|
||||
count = Avatar.objects.filter(emailuser=self.emailuser).count()
|
||||
if AVATAR_MAX_AVATARS_PER_USER > 1 and \
|
||||
count >= AVATAR_MAX_AVATARS_PER_USER:
|
||||
raise forms.ValidationError(
|
||||
_(u"You already have %(nb_avatars)d avatars, and the maximum allowed is %(nb_max_avatars)d.") %
|
||||
{ 'nb_avatars' : count, 'nb_max_avatars' : AVATAR_MAX_AVATARS_PER_USER})
|
||||
return
|
||||
|
||||
|
||||
class PrimaryAvatarForm(forms.Form):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop('user')
|
||||
size = kwargs.pop('size', AVATAR_DEFAULT_SIZE)
|
||||
avatars = kwargs.pop('avatars')
|
||||
super(PrimaryAvatarForm, self).__init__(*args, **kwargs)
|
||||
self.fields['choice'] = forms.ChoiceField(
|
||||
choices=[(c.id, avatar_img(c, size)) for c in avatars],
|
||||
widget=widgets.RadioSelect)
|
||||
|
||||
class DeleteAvatarForm(forms.Form):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop('user')
|
||||
size = kwargs.pop('size', AVATAR_DEFAULT_SIZE)
|
||||
avatars = kwargs.pop('avatars')
|
||||
super(DeleteAvatarForm, self).__init__(*args, **kwargs)
|
||||
self.fields['choices'] = forms.MultipleChoiceField(
|
||||
choices=[(c.id, avatar_img(c, size)) for c in avatars],
|
||||
widget=widgets.CheckboxSelectMultiple)
|
4
avatar/i18n.sh.template
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
django-admin.py makemessages -l zh_CN -e py,html
|
||||
django-admin.py compilemessages
|
BIN
avatar/locale/de/LC_MESSAGES/django.mo
Normal file
131
avatar/locale/de/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,131 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-03-16 15:19+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#: forms.py:33
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(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:37
|
||||
#, 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"
|
||||
|
||||
#: forms.py:43
|
||||
#, 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. Das maximale "
|
||||
"Anzahl ist %(nb_max_avatars)d."
|
||||
|
||||
#: models.py:71
|
||||
#, python-format
|
||||
msgid "Avatar for %s"
|
||||
msgstr "Avatar für %s"
|
||||
|
||||
#: views.py:90
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "Erfolgreich einen neuen Avatar hochgeladen."
|
||||
|
||||
#: views.py:128
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "Erfolgreich Ihren Avatar aktualisiert."
|
||||
|
||||
#: views.py:166
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
msgstr "Erfolgreich den Avatar gelöscht."
|
||||
|
||||
#: management/__init__.py:9
|
||||
msgid "Avatar Updated"
|
||||
msgstr "Avatar aktualisiert"
|
||||
|
||||
#: management/__init__.py:9
|
||||
msgid "your avatar has been updated"
|
||||
msgstr "Ihr Avatar wurde aktualisiert"
|
||||
|
||||
#: management/__init__.py:10
|
||||
msgid "Friend Updated Avatar"
|
||||
msgstr "Freund aktualisierte Avatar"
|
||||
|
||||
#: management/__init__.py:10
|
||||
msgid "a friend has updated their avatar"
|
||||
msgstr "Avatar eines Freundes wurde aktualisiert"
|
||||
|
||||
#: templates/avatar/add.html:5 templates/avatar/change.html:5
|
||||
msgid "Your current avatar: "
|
||||
msgstr "Ihr aktueller Avatar: "
|
||||
|
||||
#: templates/avatar/add.html:8 templates/avatar/change.html:8
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr ""
|
||||
"Sie haben noch keinen Avatar hochgeladen. Bitte laden Sie nun einen hoch."
|
||||
|
||||
#: templates/avatar/add.html:12 templates/avatar/change.html:19
|
||||
msgid "Upload New Image"
|
||||
msgstr "Neues Bild hochladen"
|
||||
|
||||
#: templates/avatar/change.html:14
|
||||
msgid "Choose new Default"
|
||||
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."
|
||||
|
||||
#: 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 ""
|
||||
"Sie haben keine Avatare zum Löschen. Bitte <a href=\"%(avatar_change_url)s"
|
||||
"\">laden Sie einen hoch</a>."
|
||||
|
||||
#: templates/avatar/confirm_delete.html:14
|
||||
msgid "Delete These"
|
||||
msgstr "Auswahl löschen"
|
||||
|
||||
#: 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> hat den Avatar aktualisiert "
|
||||
"<a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr ""
|
||||
"Sie haben Ihren Avatar aktualisiert <a href=\"%(avatar_url)s\">%(avatar)s</"
|
||||
"a>."
|
||||
|
||||
#: templatetags/avatar_tags.py:40
|
||||
msgid "Default Avatar"
|
||||
msgstr "Standard-Avatar"
|
BIN
avatar/locale/fr/LC_MESSAGES/django.mo
Normal file
111
avatar/locale/fr/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,111 @@
|
||||
# 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: 2010-03-26 18:29+0100\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"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: forms.py:33
|
||||
#, python-format
|
||||
msgid "%(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:37
|
||||
#, 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:43
|
||||
#, python-format
|
||||
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."
|
||||
|
||||
#: models.py:72
|
||||
#, python-format
|
||||
msgid "Avatar for %s"
|
||||
msgstr "Avatar pour %s"
|
||||
|
||||
#: views.py:90
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "Votre nouveau avatar a été uploadé avec succès."
|
||||
|
||||
#: views.py:128
|
||||
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."
|
||||
|
||||
#: management/__init__.py:9
|
||||
msgid "Avatar Updated"
|
||||
msgstr "Avatar mis à jour"
|
||||
|
||||
#: management/__init__.py:9
|
||||
msgid "your avatar has been updated"
|
||||
msgstr "votre avatar a été mis à jour"
|
||||
|
||||
#: management/__init__.py:10
|
||||
msgid "Friend Updated Avatar"
|
||||
msgstr "Avatar mis à jour par un ami"
|
||||
|
||||
#: management/__init__.py:10
|
||||
msgid "a friend has updated their avatar"
|
||||
msgstr "un ami a mis à jour son avatar"
|
||||
|
||||
#: templates/avatar/add.html:5
|
||||
#: templates/avatar/change.html:5
|
||||
msgid "Your current avatar: "
|
||||
msgstr "Votre avatar actuel:"
|
||||
|
||||
#: templates/avatar/add.html:8
|
||||
#: templates/avatar/change.html:8
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr "Vous n'avez pas encore ajouté d'avatar. Veuillez le faire maintenant."
|
||||
|
||||
#: templates/avatar/add.html:12
|
||||
#: templates/avatar/change.html:19
|
||||
msgid "Upload New Image"
|
||||
msgstr "Ajouter une nouvelle image"
|
||||
|
||||
#: templates/avatar/change.html:14
|
||||
msgid "Choose new Default"
|
||||
msgstr "Choisir le nouvel avatar par défaut"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:5
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr "Veuillez sélectionner les avatars que vous souhaitez effacer"
|
||||
|
||||
#: 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 "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"
|
||||
msgstr "Effacer"
|
||||
|
||||
#: 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 mis à jour son avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: 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:40
|
||||
msgid "Default Avatar"
|
||||
msgstr "Avatar par défaut"
|
||||
|
BIN
avatar/locale/pt_BR/LC_MESSAGES/django.mo
Normal file
68
avatar/locale/pt_BR/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,68 @@
|
||||
# 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: 2009-08-09 04:13-0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: models.py:32
|
||||
#, python-format
|
||||
msgid "Avatar for %s"
|
||||
msgstr "Avatar para %s"
|
||||
|
||||
#: views.py:68
|
||||
msgid "Successfully uploaded a new avatar."
|
||||
msgstr "Nova foto de perfil enviada com sucesso."
|
||||
|
||||
#: views.py:76
|
||||
msgid "Successfully updated your avatar."
|
||||
msgstr "Sua foto de perfil foi atualizada com sucesso."
|
||||
|
||||
#: views.py:114
|
||||
msgid "Successfully deleted the requested avatars."
|
||||
msgstr "As fotos de perfil selecionadas foram excluídas com sucesso."
|
||||
|
||||
#: management/__init__.py:9
|
||||
msgid "Avatar Updated"
|
||||
msgstr "Foto de Perfil Atualizada"
|
||||
|
||||
#: management/__init__.py:9
|
||||
msgid "avatar have been updated"
|
||||
msgstr "foto de perfil foi atualizada"
|
||||
|
||||
#: management/__init__.py:10
|
||||
msgid "Friend Updated Avatar"
|
||||
msgstr "Amigo Atualizou Foto de Perfil"
|
||||
|
||||
#: management/__init__.py:10
|
||||
msgid "a friend has updated his avatar"
|
||||
msgstr "um amigo atualizou a foto de perfil"
|
||||
|
||||
#: templates/notifications/avatar_friend_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> has updated his avatar <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr ""
|
||||
"<a href=\"%(user_url)s\">%(avatar_creator)s</a> atualizou a foto de perfil <a "
|
||||
"href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
|
||||
#: templates/notifications/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "A new tribe <a href=\"%(avatar_url)s\">%(avatar)s</a> has been created."
|
||||
msgstr "Uma nova foto de perfil <a href=\"%(avatar_url)s\">%(avatar)s</a> foi criada."
|
||||
|
||||
#: templatetags/avatar_tags.py:47
|
||||
msgid "Default Avatar"
|
||||
msgstr "Foto de Perfil Padrão"
|
BIN
avatar/locale/zh_CN/LC_MESSAGES/django.mo
Normal file
101
avatar/locale/zh_CN/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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: 2012-05-24 11:16+0800\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"
|
||||
|
||||
#: forms.py:35
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(ext)s is an invalid file extension. Authorized extensions are : "
|
||||
"%(valid_exts_list)s"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:39
|
||||
#, 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:45
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You already have %(nb_avatars)d avatars, and the maximum allowed is "
|
||||
"%(nb_max_avatars)d."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:57
|
||||
msgid "choice"
|
||||
msgstr "从已有头像中选择"
|
||||
|
||||
#: forms.py:68
|
||||
msgid "choices"
|
||||
msgstr "从已有头像中选择"
|
||||
|
||||
#: models.py:79
|
||||
#, python-format
|
||||
msgid "Avatar for %s"
|
||||
msgstr ""
|
||||
|
||||
#: templates/avatar/add.html:5 templates/avatar/change.html:6
|
||||
msgid "Your current avatar: "
|
||||
msgstr "当前头像:"
|
||||
|
||||
#: templates/avatar/add.html:8 templates/avatar/change.html:9
|
||||
msgid "You haven't uploaded an avatar yet. Please upload one now."
|
||||
msgstr "您还没有上传自己的头像。请上传。"
|
||||
|
||||
#: templates/avatar/add.html:12 templates/avatar/change.html:20
|
||||
msgid "Upload New Image"
|
||||
msgstr "提交"
|
||||
|
||||
#: templates/avatar/change.html:15
|
||||
msgid "Choose new Default"
|
||||
msgstr "确定"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:6
|
||||
msgid "Please select the avatars that you would like to delete."
|
||||
msgstr "请选择要删除的头像。"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:9
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s"
|
||||
"\">upload one</a> now."
|
||||
msgstr ""
|
||||
"您还没有上传自己的头像。现在 <a href=\"%(avatar_change_url)s\">上传一个</a>。"
|
||||
|
||||
#: templates/avatar/confirm_delete.html:15
|
||||
msgid "Delete These"
|
||||
msgstr "删除"
|
||||
|
||||
#: templates/notification/avatar_friend_updated/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 ""
|
||||
|
||||
#: templates/notification/avatar_updated/notice.html:2
|
||||
#, python-format
|
||||
msgid "You have updated your avatar <a href=\"%(avatar_url)s\">%(avatar)s</a>."
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/avatar_tags.py:48
|
||||
msgid "Default Avatar"
|
||||
msgstr ""
|
0
avatar/management/__init__.py
Normal file
1
avatar/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
14
avatar/management/commands/rebuild_avatars.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
from avatar.models import Avatar
|
||||
from avatar.settings import AUTO_GENERATE_AVATAR_SIZES
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Regenerates avatar thumbnails for the sizes specified in " + \
|
||||
"settings.AUTO_GENERATE_AVATAR_SIZES."
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
for avatar in Avatar.objects.all():
|
||||
for size in AUTO_GENERATE_AVATAR_SIZES:
|
||||
print "Rebuilding Avatar id=%s at size %s." % (avatar.id, size)
|
||||
avatar.create_thumbnail(size)
|
BIN
avatar/media/avatar/img/default.jpg
Normal file
After Width: | Height: | Size: 3.4 KiB |
142
avatar/models.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
from django.utils.encoding import smart_str
|
||||
from django.db.models import signals
|
||||
|
||||
#from django.contrib.auth.models import User
|
||||
from seahub.base.accounts import CcnetUser
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
dir(StringIO) # Placate PyFlakes
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
dir(Image) # Placate PyFlakes
|
||||
except ImportError:
|
||||
import Image
|
||||
|
||||
from avatar.util import invalidate_cache
|
||||
from avatar.settings import (AVATAR_STORAGE_DIR, AVATAR_RESIZE_METHOD,
|
||||
AVATAR_MAX_AVATARS_PER_USER, AVATAR_THUMB_FORMAT,
|
||||
AVATAR_HASH_USERDIRNAMES, AVATAR_HASH_FILENAMES,
|
||||
AVATAR_THUMB_QUALITY, AUTO_GENERATE_AVATAR_SIZES)
|
||||
|
||||
|
||||
def avatar_file_path(instance=None, filename=None, size=None, ext=None):
|
||||
tmppath = [AVATAR_STORAGE_DIR]
|
||||
if AVATAR_HASH_USERDIRNAMES:
|
||||
tmp = md5_constructor(instance.user.username).hexdigest()
|
||||
tmppath.extend([tmp[0], tmp[1], instance.emailuser])
|
||||
else:
|
||||
tmppath.append(instance.emailuser)
|
||||
if not filename:
|
||||
# Filename already stored in database
|
||||
filename = instance.avatar.name
|
||||
if ext and AVATAR_HASH_FILENAMES:
|
||||
# An extension was provided, probably because the thumbnail
|
||||
# is in a different format than the file. Use it. Because it's
|
||||
# only enabled if AVATAR_HASH_FILENAMES is true, we can trust
|
||||
# it won't conflict with another filename
|
||||
(root, oldext) = os.path.splitext(filename)
|
||||
filename = root + "." + ext
|
||||
else:
|
||||
# File doesn't exist yet
|
||||
if AVATAR_HASH_FILENAMES:
|
||||
(root, ext) = os.path.splitext(filename)
|
||||
filename = md5_constructor(smart_str(filename)).hexdigest()
|
||||
filename = filename + ext
|
||||
if size:
|
||||
tmppath.extend(['resized', str(size)])
|
||||
tmppath.append(os.path.basename(filename))
|
||||
return os.path.join(*tmppath)
|
||||
|
||||
def find_extension(format):
|
||||
format = format.lower()
|
||||
|
||||
if format == 'jpeg':
|
||||
format = 'jpg'
|
||||
|
||||
return format
|
||||
|
||||
class Avatar(models.Model):
|
||||
emailuser = models.CharField(max_length=255)
|
||||
primary = models.BooleanField(default=False)
|
||||
avatar = models.ImageField(max_length=1024, upload_to=avatar_file_path, blank=True)
|
||||
date_uploaded = models.DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
def __unicode__(self):
|
||||
return _(u'Avatar for %s') % self.emailuser
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
avatars = Avatar.objects.filter(emailuser=self.emailuser)
|
||||
if self.pk:
|
||||
avatars = avatars.exclude(pk=self.pk)
|
||||
if AVATAR_MAX_AVATARS_PER_USER > 1:
|
||||
if self.primary:
|
||||
avatars = avatars.filter(primary=True)
|
||||
avatars.update(primary=False)
|
||||
else:
|
||||
avatars.delete()
|
||||
invalidate_cache(self.emailuser)
|
||||
super(Avatar, self).save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
invalidate_cache(self.emailuser)
|
||||
super(Avatar, self).delete(*args, **kwargs)
|
||||
|
||||
def thumbnail_exists(self, size):
|
||||
return self.avatar.storage.exists(self.avatar_name(size))
|
||||
|
||||
def create_thumbnail(self, size, quality=None):
|
||||
# invalidate the cache of the thumbnail with the given size first
|
||||
invalidate_cache(self.emailuser, size)
|
||||
try:
|
||||
orig = self.avatar.storage.open(self.avatar.name, 'rb').read()
|
||||
image = Image.open(StringIO(orig))
|
||||
except IOError:
|
||||
return # What should we do here? Render a "sorry, didn't work" img?
|
||||
quality = quality or AVATAR_THUMB_QUALITY
|
||||
(w, h) = image.size
|
||||
if w != size or h != size:
|
||||
if w > h:
|
||||
diff = (w - h) / 2
|
||||
image = image.crop((diff, 0, w - diff, h))
|
||||
else:
|
||||
diff = (h - w) / 2
|
||||
image = image.crop((0, diff, w, h - diff))
|
||||
if image.mode != "RGB":
|
||||
image = image.convert("RGB")
|
||||
image = image.resize((size, size), AVATAR_RESIZE_METHOD)
|
||||
thumb = StringIO()
|
||||
image.save(thumb, AVATAR_THUMB_FORMAT, quality=quality)
|
||||
thumb_file = ContentFile(thumb.getvalue())
|
||||
else:
|
||||
thumb_file = ContentFile(orig)
|
||||
thumb = self.avatar.storage.save(self.avatar_name(size), thumb_file)
|
||||
|
||||
def avatar_url(self, size):
|
||||
return self.avatar.storage.url(self.avatar_name(size))
|
||||
|
||||
def avatar_name(self, size):
|
||||
ext = find_extension(AVATAR_THUMB_FORMAT)
|
||||
return avatar_file_path(
|
||||
instance=self,
|
||||
size=size,
|
||||
ext=ext
|
||||
)
|
||||
|
||||
|
||||
def create_default_thumbnails(instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
for size in AUTO_GENERATE_AVATAR_SIZES:
|
||||
instance.create_thumbnail(size)
|
||||
|
||||
signals.post_save.connect(create_default_thumbnails, sender=Avatar)
|
23
avatar/settings.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
dir(Image) # Placate PyFlakes
|
||||
except ImportError:
|
||||
import Image
|
||||
|
||||
AVATAR_DEFAULT_SIZE = getattr(settings, 'AVATAR_DEFAULT_SIZE', 80)
|
||||
AUTO_GENERATE_AVATAR_SIZES = getattr(settings, 'AUTO_GENERATE_AVATAR_SIZES', (AVATAR_DEFAULT_SIZE,))
|
||||
AVATAR_RESIZE_METHOD = getattr(settings, 'AVATAR_RESIZE_METHOD', Image.ANTIALIAS)
|
||||
AVATAR_STORAGE_DIR = getattr(settings, 'AVATAR_STORAGE_DIR', 'avatars')
|
||||
AVATAR_GRAVATAR_BACKUP = getattr(settings, 'AVATAR_GRAVATAR_BACKUP', True)
|
||||
AVATAR_GRAVATAR_DEFAULT = getattr(settings, 'AVATAR_GRAVATAR_DEFAULT', None)
|
||||
AVATAR_DEFAULT_URL = getattr(settings, 'AVATAR_DEFAULT_URL', 'avatar/img/default.jpg')
|
||||
AVATAR_MAX_AVATARS_PER_USER = getattr(settings, 'AVATAR_MAX_AVATARS_PER_USER', 42)
|
||||
AVATAR_MAX_SIZE = getattr(settings, 'AVATAR_MAX_SIZE', 1024 * 1024)
|
||||
AVATAR_THUMB_FORMAT = getattr(settings, 'AVATAR_THUMB_FORMAT', "JPEG")
|
||||
AVATAR_THUMB_QUALITY = getattr(settings, 'AVATAR_THUMB_QUALITY', 85)
|
||||
AVATAR_HASH_FILENAMES = getattr(settings, 'AVATAR_HASH_FILENAMES', False)
|
||||
AVATAR_HASH_USERDIRNAMES = getattr(settings, 'AVATAR_HASH_USERDIRNAMES', False)
|
||||
AVATAR_ALLOWED_FILE_EXTS = getattr(settings, 'AVATAR_ALLOWED_FILE_EXTS', None)
|
||||
AVATAR_CACHE_TIMEOUT = getattr(settings, 'AVATAR_CACHE_TIMEOUT', 60*60)
|
4
avatar/signals.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import django.dispatch
|
||||
|
||||
|
||||
avatar_updated = django.dispatch.Signal(providing_args=["user", "avatar"])
|
14
avatar/templates/avatar/add.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load i18n avatar_tags %}
|
||||
|
||||
{% block main_panel %}
|
||||
<p>{% trans "Your current avatar: " %}</p>
|
||||
{% avatar user %}
|
||||
{% if not avatars %}
|
||||
<p>{% trans "You haven't uploaded an avatar yet. Please upload one now." %}</p>
|
||||
{% endif %}
|
||||
<form enctype="multipart/form-data" method="POST" action="{% url avatar_add %}">
|
||||
{{ upload_avatar_form.as_p }}
|
||||
<p>{% csrf_token %}<input type="submit" value="{% trans "Upload New Image" %}" /></p>
|
||||
</form>
|
||||
{% endblock %}
|
8
avatar/templates/avatar/base.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}django-avatar{% endblock %}</title>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
31
avatar/templates/avatar/change.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load i18n avatar_tags %}
|
||||
|
||||
{% block main_panel %}
|
||||
<h2>修改头像</h2>
|
||||
<h3>{% trans "Your current avatar: " %}</h3>
|
||||
{% avatar user %}
|
||||
|
||||
{% comment %}
|
||||
{% if not avatars %}
|
||||
<p>{% trans "You haven't uploaded an avatar yet. Please upload one now." %}</p>
|
||||
{% else %}
|
||||
<form method="POST" action="{% url avatar_change %}">
|
||||
<label>从已有头像中选择:</label>
|
||||
{% for boundfield in primary_avatar_form %}
|
||||
{{ boundfield }}
|
||||
{% endfor %}
|
||||
<p>{% csrf_token %}<input type="submit" value="{% trans "Choose new Default" %}" /></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endcomment %}
|
||||
|
||||
<h3 id="upload-new-avatar-hd">上传新头像:</h3>
|
||||
<form enctype="multipart/form-data" method="POST" action="{% url avatar_add %}">
|
||||
{% for boundfield in upload_avatar_form %}
|
||||
{{ boundfield }}
|
||||
{% endfor %}
|
||||
<br />
|
||||
<input type="submit" value="{% trans "Upload New Image" %}" />
|
||||
</form>
|
||||
{% endblock %}
|
21
avatar/templates/avatar/confirm_delete.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block main_panel %}
|
||||
<!--
|
||||
<p>{% trans "Please select the avatars that you would like to delete." %}</p>
|
||||
-->
|
||||
<h2>删除头像</h2>
|
||||
{% if not avatars %}
|
||||
{% url avatar_change as avatar_change_url %}
|
||||
<p>{% blocktrans %}You have no avatars to delete. Please <a href="{{ avatar_change_url }}">upload one</a> now.{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<form method="POST" action="{% url avatar_delete %}">
|
||||
<lable>请选择要删除的头像:</lable>
|
||||
{% for boundfield in delete_avatar_form %}
|
||||
{{ boundfield }}
|
||||
{% endfor %}
|
||||
<p>{% csrf_token %}<input type="submit" value="{% trans "Delete These" %}" /></p>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@@ -0,0 +1,4 @@
|
||||
{% load i18n %}{% blocktrans with user as avatar_creator and avatar.get_absolute_url as avatar_url %}{{ avatar_creator }} has updated their avatar {{ avatar }}.
|
||||
|
||||
http://{{ current_site }}{{ avatar_url }}
|
||||
{% endblocktrans %}
|
@@ -0,0 +1,2 @@
|
||||
{% load i18n %}{% url profile_detail username=user.username as user_url %}
|
||||
{% 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 %}
|
4
avatar/templates/notification/avatar_updated/full.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
{% load i18n %}{% blocktrans with avatar.get_absolute_url as avatar_url %}Your avatar has been updated. {{ avatar }}
|
||||
|
||||
http://{{ current_site }}{{ avatar_url }}
|
||||
{% endblocktrans %}
|
2
avatar/templates/notification/avatar_updated/notice.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% load i18n %}
|
||||
{% blocktrans with user as avatar_creator and avatar.get_absolute_url as avatar_url %}You have updated your avatar <a href="{{ avatar_url }}">{{ avatar }}</a>.{% endblocktrans %}
|
0
avatar/templatetags/__init__.py
Normal file
72
avatar/templatetags/avatar_tags.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import urllib
|
||||
|
||||
from django import template
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.base.accounts import CcnetUser
|
||||
from seaserv import get_ccnetuser
|
||||
|
||||
from avatar.settings import (AVATAR_GRAVATAR_BACKUP, AVATAR_GRAVATAR_DEFAULT,
|
||||
AVATAR_DEFAULT_SIZE)
|
||||
from avatar.util import get_primary_avatar, get_default_avatar_url, cache_result
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@cache_result
|
||||
@register.simple_tag
|
||||
def avatar_url(user, size=AVATAR_DEFAULT_SIZE):
|
||||
avatar = get_primary_avatar(user, size=size)
|
||||
if avatar:
|
||||
return avatar.avatar_url(size)
|
||||
else:
|
||||
if AVATAR_GRAVATAR_BACKUP:
|
||||
params = {'s': str(size)}
|
||||
if AVATAR_GRAVATAR_DEFAULT:
|
||||
params['d'] = AVATAR_GRAVATAR_DEFAULT
|
||||
return "http://www.gravatar.com/avatar/%s/?%s" % (
|
||||
md5_constructor(user.email).hexdigest(),
|
||||
urllib.urlencode(params))
|
||||
else:
|
||||
return get_default_avatar_url()
|
||||
|
||||
@cache_result
|
||||
@register.simple_tag
|
||||
def avatar(user, size=AVATAR_DEFAULT_SIZE):
|
||||
if not isinstance(user, CcnetUser):
|
||||
try:
|
||||
user = get_ccnetuser(username=user)
|
||||
alt = unicode(user)
|
||||
url = avatar_url(user, size)
|
||||
except:
|
||||
url = get_default_avatar_url()
|
||||
alt = _("Default Avatar")
|
||||
else:
|
||||
alt = unicode(user)
|
||||
url = avatar_url(user, size)
|
||||
return """<img src="%s" alt="%s" width="%s" height="%s" class="avatar" />""" % (url, alt,
|
||||
size, size)
|
||||
|
||||
@cache_result
|
||||
@register.simple_tag
|
||||
def primary_avatar(user, size=AVATAR_DEFAULT_SIZE):
|
||||
"""
|
||||
This tag tries to get the default avatar for a user without doing any db
|
||||
requests. It achieve this by linking to a special view that will do all the
|
||||
work for us. If that special view is then cached by a CDN for instance,
|
||||
we will avoid many db calls.
|
||||
"""
|
||||
alt = unicode(user)
|
||||
url = reverse('avatar_render_primary', kwargs={'user' : user, 'size' : size})
|
||||
return """<img src="%s" alt="%s" width="%s" height="%s" />""" % (url, alt,
|
||||
size, size)
|
||||
|
||||
@cache_result
|
||||
@register.simple_tag
|
||||
def render_avatar(avatar, size=AVATAR_DEFAULT_SIZE):
|
||||
if not avatar.thumbnail_exists(size):
|
||||
avatar.create_thumbnail(size)
|
||||
return """<img src="%s" alt="%s" width="%s" height="%s" />""" % (
|
||||
avatar.avatar_url(size), str(avatar), size, size)
|
BIN
avatar/testdata/imagefilewithoutext
vendored
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
avatar/testdata/imagefilewithwrongext.ogg
vendored
Normal file
After Width: | Height: | Size: 136 KiB |
1070
avatar/testdata/nonimagefile
vendored
Normal file
BIN
avatar/testdata/test.png
vendored
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
avatar/testdata/testbig.png
vendored
Normal file
After Width: | Height: | Size: 1.1 MiB |
135
avatar/tests.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import os.path
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from avatar.settings import AVATAR_DEFAULT_URL, AVATAR_MAX_AVATARS_PER_USER
|
||||
from avatar.util import get_primary_avatar
|
||||
from avatar.models import Avatar
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
dir(Image) # Placate PyFlakes
|
||||
except ImportError:
|
||||
import Image
|
||||
|
||||
|
||||
def upload_helper(o, filename):
|
||||
f = open(os.path.join(o.testdatapath, filename), "rb")
|
||||
response = o.client.post(reverse('avatar_add'), {
|
||||
'avatar': f,
|
||||
}, follow=True)
|
||||
f.close()
|
||||
return response
|
||||
|
||||
class AvatarUploadTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.testdatapath = os.path.join(os.path.dirname(__file__), "testdata")
|
||||
self.user = User.objects.create_user('test', 'lennon@thebeatles.com', 'testpassword')
|
||||
self.user.save()
|
||||
self.client.login(username='test', password='testpassword')
|
||||
Image.init()
|
||||
|
||||
def testNonImageUpload(self):
|
||||
response = upload_helper(self, "nonimagefile")
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failIfEqual(response.context['upload_avatar_form'].errors, {})
|
||||
|
||||
def testNormalImageUpload(self):
|
||||
response = upload_helper(self, "test.png")
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 1)
|
||||
self.failUnlessEqual(response.context['upload_avatar_form'].errors, {})
|
||||
avatar = get_primary_avatar(self.user)
|
||||
self.failIfEqual(avatar, None)
|
||||
|
||||
def testImageWithoutExtension(self):
|
||||
# use with AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
|
||||
response = upload_helper(self, "imagefilewithoutext")
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 0) # Redirect only if it worked
|
||||
self.failIfEqual(response.context['upload_avatar_form'].errors, {})
|
||||
|
||||
def testImageWithWrongExtension(self):
|
||||
# use with AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png')
|
||||
response = upload_helper(self, "imagefilewithwrongext.ogg")
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 0) # Redirect only if it worked
|
||||
self.failIfEqual(response.context['upload_avatar_form'].errors, {})
|
||||
|
||||
def testImageTooBig(self):
|
||||
# use with AVATAR_MAX_SIZE = 1024 * 1024
|
||||
response = upload_helper(self, "testbig.png")
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 0) # Redirect only if it worked
|
||||
self.failIfEqual(response.context['upload_avatar_form'].errors, {})
|
||||
|
||||
def testDefaultUrl(self):
|
||||
response = self.client.get(reverse('avatar_render_primary', kwargs={
|
||||
'user': self.user.username,
|
||||
'size': 80,
|
||||
}))
|
||||
loc = response['Location']
|
||||
base_url = getattr(settings, 'STATIC_URL', None)
|
||||
if not base_url:
|
||||
base_url = settings.MEDIA_URL
|
||||
self.assertTrue(base_url in loc)
|
||||
self.assertTrue(loc.endswith(AVATAR_DEFAULT_URL))
|
||||
|
||||
def testNonExistingUser(self):
|
||||
a = get_primary_avatar("nonexistinguser")
|
||||
self.failUnlessEqual(a, None)
|
||||
|
||||
def testThereCanBeOnlyOnePrimaryAvatar(self):
|
||||
for i in range(1, 10):
|
||||
self.testNormalImageUpload()
|
||||
count = Avatar.objects.filter(user=self.user, primary=True).count()
|
||||
self.failUnlessEqual(count, 1)
|
||||
|
||||
def testDeleteAvatar(self):
|
||||
self.testNormalImageUpload()
|
||||
avatar = Avatar.objects.filter(user=self.user)
|
||||
self.failUnlessEqual(len(avatar), 1)
|
||||
response = self.client.post(reverse('avatar_delete'), {
|
||||
'choices': [avatar[0].id],
|
||||
}, follow=True)
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 1)
|
||||
count = Avatar.objects.filter(user=self.user).count()
|
||||
self.failUnlessEqual(count, 0)
|
||||
|
||||
def testDeletePrimaryAvatarAndNewPrimary(self):
|
||||
self.testThereCanBeOnlyOnePrimaryAvatar()
|
||||
primary = get_primary_avatar(self.user)
|
||||
oid = primary.id
|
||||
response = self.client.post(reverse('avatar_delete'), {
|
||||
'choices': [oid],
|
||||
})
|
||||
primaries = Avatar.objects.filter(user=self.user, primary=True)
|
||||
self.failUnlessEqual(len(primaries), 1)
|
||||
self.failIfEqual(oid, primaries[0].id)
|
||||
avatars = Avatar.objects.filter(user=self.user)
|
||||
self.failUnlessEqual(avatars[0].id, primaries[0].id)
|
||||
|
||||
def testTooManyAvatars(self):
|
||||
for i in range(0, AVATAR_MAX_AVATARS_PER_USER):
|
||||
self.testNormalImageUpload()
|
||||
count_before = Avatar.objects.filter(user=self.user).count()
|
||||
response = upload_helper(self, "test.png")
|
||||
count_after = Avatar.objects.filter(user=self.user).count()
|
||||
self.failUnlessEqual(response.status_code, 200)
|
||||
self.failUnlessEqual(len(response.redirect_chain), 0) # Redirect only if it worked
|
||||
self.failIfEqual(response.context['upload_avatar_form'].errors, {})
|
||||
self.failUnlessEqual(count_before, count_after)
|
||||
|
||||
# def testAvatarOrder
|
||||
# def testReplaceAvatarWhenMaxIsOne
|
||||
# def testHashFileName
|
||||
# def testHashUserName
|
||||
# def testChangePrimaryAvatar
|
||||
# def testDeleteThumbnailAndRecreation
|
||||
# def testAutomaticThumbnailCreation
|
8
avatar/urls.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('avatar.views',
|
||||
url('^add/$', 'add', name='avatar_add'),
|
||||
url('^change/$', 'change', name='avatar_change'),
|
||||
# url('^delete/$', 'delete', name='avatar_delete'),
|
||||
url('^render_primary/(?P<user>[\+\w]+)/(?P<size>[\d]+)/$', 'render_primary', name='avatar_render_primary'),
|
||||
)
|
82
avatar/util.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from seahub.base.accounts import CcnetUser
|
||||
|
||||
from seaserv import get_ccnetuser
|
||||
|
||||
from avatar.settings import (AVATAR_DEFAULT_URL, AVATAR_CACHE_TIMEOUT,
|
||||
AUTO_GENERATE_AVATAR_SIZES, AVATAR_DEFAULT_SIZE)
|
||||
|
||||
cached_funcs = set()
|
||||
|
||||
def get_cache_key(user_or_username, size, prefix):
|
||||
"""
|
||||
Returns a cache key consisten of a username and image size.
|
||||
"""
|
||||
if isinstance(user_or_username, CcnetUser):
|
||||
user_or_username = user_or_username.username
|
||||
return '%s_%s_%s' % (prefix, user_or_username, size)
|
||||
|
||||
def cache_result(func):
|
||||
"""
|
||||
Decorator to cache the result of functions that take a ``user`` and a
|
||||
``size`` value.
|
||||
"""
|
||||
def cache_set(key, value):
|
||||
cache.set(key, value, AVATAR_CACHE_TIMEOUT)
|
||||
return value
|
||||
|
||||
def cached_func(user, size):
|
||||
prefix = func.__name__
|
||||
cached_funcs.add(prefix)
|
||||
key = get_cache_key(user, size, prefix=prefix)
|
||||
return cache.get(key) or cache_set(key, func(user, size))
|
||||
return cached_func
|
||||
|
||||
def invalidate_cache(user, size=None):
|
||||
"""
|
||||
Function to be called when saving or changing an user's avatars.
|
||||
"""
|
||||
sizes = set(AUTO_GENERATE_AVATAR_SIZES)
|
||||
if size is not None:
|
||||
sizes.add(size)
|
||||
for prefix in cached_funcs:
|
||||
for size in sizes:
|
||||
cache.delete(get_cache_key(user, size, prefix))
|
||||
|
||||
def get_default_avatar_url():
|
||||
base_url = getattr(settings, 'STATIC_URL', None)
|
||||
if not base_url:
|
||||
base_url = getattr(settings, 'MEDIA_URL', '')
|
||||
# Don't use base_url if the default avatar url starts with http:// of https://
|
||||
if AVATAR_DEFAULT_URL.startswith('http://') or AVATAR_DEFAULT_URL.startswith('https://'):
|
||||
return AVATAR_DEFAULT_URL
|
||||
# We'll be nice and make sure there are no duplicated forward slashes
|
||||
ends = base_url.endswith('/')
|
||||
begins = AVATAR_DEFAULT_URL.startswith('/')
|
||||
if ends and begins:
|
||||
base_url = base_url[:-1]
|
||||
elif not ends and not begins:
|
||||
return '%s/%s' % (base_url, AVATAR_DEFAULT_URL)
|
||||
return '%s%s' % (base_url, AVATAR_DEFAULT_URL)
|
||||
|
||||
def get_primary_avatar(user, size=AVATAR_DEFAULT_SIZE):
|
||||
if not isinstance(user, CcnetUser):
|
||||
try:
|
||||
user = get_ccnetuser(username=user)
|
||||
except:
|
||||
return None
|
||||
try:
|
||||
# Order by -primary first; this means if a primary=True avatar exists
|
||||
# it will be first, and then ordered by date uploaded, otherwise a
|
||||
# primary=False avatar will be first. Exactly the fallback behavior we
|
||||
# want.
|
||||
from seahub.avatar.models import Avatar
|
||||
avatar = Avatar.objects.filter(emailuser=user.email, primary=1)[0]
|
||||
except IndexError:
|
||||
avatar = None
|
||||
if avatar:
|
||||
if not avatar.thumbnail_exists(size):
|
||||
avatar.create_thumbnail(size)
|
||||
return avatar
|
181
avatar/views.py
Normal file
@@ -0,0 +1,181 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from avatar.forms import PrimaryAvatarForm, DeleteAvatarForm, UploadAvatarForm
|
||||
from avatar.models import Avatar
|
||||
from avatar.settings import AVATAR_MAX_AVATARS_PER_USER, AVATAR_DEFAULT_SIZE
|
||||
from avatar.signals import avatar_updated
|
||||
from avatar.util import get_primary_avatar, get_default_avatar_url, \
|
||||
invalidate_cache
|
||||
|
||||
|
||||
def _get_next(request):
|
||||
"""
|
||||
The part that's the least straightforward about views in this module is how they
|
||||
determine their redirects after they have finished computation.
|
||||
|
||||
In short, they will try and determine the next place to go in the following order:
|
||||
|
||||
1. If there is a variable named ``next`` in the *POST* parameters, the view will
|
||||
redirect to that variable's value.
|
||||
2. If there is a variable named ``next`` in the *GET* parameters, the view will
|
||||
redirect to that variable's value.
|
||||
3. If Django can determine the previous page from the HTTP headers, the view will
|
||||
redirect to that previous page.
|
||||
"""
|
||||
next = request.POST.get('next', request.GET.get('next',
|
||||
request.META.get('HTTP_REFERER', None)))
|
||||
if not next:
|
||||
next = request.path
|
||||
return next
|
||||
|
||||
def _get_avatars(user):
|
||||
# Default set. Needs to be sliced, but that's it. Keep the natural order.
|
||||
avatars = Avatar.objects.filter(emailuser=user.email)
|
||||
|
||||
# Current avatar
|
||||
primary_avatar = avatars.order_by('-primary')[:1]
|
||||
if primary_avatar:
|
||||
avatar = primary_avatar[0]
|
||||
else:
|
||||
avatar = None
|
||||
|
||||
if AVATAR_MAX_AVATARS_PER_USER == 1:
|
||||
avatars = primary_avatar
|
||||
else:
|
||||
# Slice the default set now that we used the queryset for the primary avatar
|
||||
avatars = avatars[:AVATAR_MAX_AVATARS_PER_USER]
|
||||
return (avatar, avatars)
|
||||
|
||||
@login_required
|
||||
def add(request, extra_context=None, next_override=None,
|
||||
upload_form=UploadAvatarForm, *args, **kwargs):
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
avatar, avatars = _get_avatars(request.user)
|
||||
upload_avatar_form = upload_form(request.POST or None,
|
||||
request.FILES or None, user=request.user)
|
||||
if request.method == "POST" and 'avatar' in request.FILES:
|
||||
if upload_avatar_form.is_valid():
|
||||
avatar = Avatar(
|
||||
emailuser = request.user.username,
|
||||
primary = True,
|
||||
)
|
||||
image_file = request.FILES['avatar']
|
||||
avatar.avatar.save(image_file.name, image_file)
|
||||
avatar.save()
|
||||
# request.user.message_set.create(
|
||||
# message=_("Successfully uploaded a new avatar."))
|
||||
avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar)
|
||||
return HttpResponseRedirect(next_override or _get_next(request))
|
||||
return render_to_response(
|
||||
'avatar/add.html',
|
||||
extra_context,
|
||||
context_instance = RequestContext(
|
||||
request,
|
||||
{ 'avatar': avatar,
|
||||
'avatars': avatars,
|
||||
'upload_avatar_form': upload_avatar_form,
|
||||
'next': next_override or _get_next(request), }
|
||||
)
|
||||
)
|
||||
|
||||
@login_required
|
||||
def change(request, extra_context=None, next_override=None,
|
||||
upload_form=UploadAvatarForm, primary_form=PrimaryAvatarForm,
|
||||
*args, **kwargs):
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
avatar, avatars = _get_avatars(request.user)
|
||||
if avatar:
|
||||
kwargs = {'initial': {'choice': avatar.id}}
|
||||
else:
|
||||
kwargs = {}
|
||||
upload_avatar_form = upload_form(user=request.user, **kwargs)
|
||||
primary_avatar_form = primary_form(request.POST or None,
|
||||
user=request.user, avatars=avatars, **kwargs)
|
||||
if request.method == "POST":
|
||||
updated = False
|
||||
if 'choice' in request.POST and primary_avatar_form.is_valid():
|
||||
avatar = Avatar.objects.get(id=
|
||||
primary_avatar_form.cleaned_data['choice'])
|
||||
avatar.primary = True
|
||||
avatar.save()
|
||||
updated = True
|
||||
# request.user.message_set.create(
|
||||
# message=_("Successfully updated your avatar."))
|
||||
if updated:
|
||||
avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar)
|
||||
return HttpResponseRedirect(next_override or _get_next(request))
|
||||
return render_to_response(
|
||||
'avatar/change.html',
|
||||
extra_context,
|
||||
context_instance = RequestContext(
|
||||
request,
|
||||
{ 'avatar': avatar,
|
||||
'avatars': avatars,
|
||||
'upload_avatar_form': upload_avatar_form,
|
||||
'primary_avatar_form': primary_avatar_form,
|
||||
'next': next_override or _get_next(request), }
|
||||
)
|
||||
)
|
||||
|
||||
@login_required
|
||||
def delete(request, extra_context=None, next_override=None, *args, **kwargs):
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
avatar, avatars = _get_avatars(request.user)
|
||||
delete_avatar_form = DeleteAvatarForm(request.POST or None,
|
||||
user=request.user, avatars=avatars)
|
||||
if request.method == 'POST':
|
||||
if delete_avatar_form.is_valid():
|
||||
ids = delete_avatar_form.cleaned_data['choices']
|
||||
if unicode(avatar.id) in ids and avatars.count() > len(ids):
|
||||
# Find the next best avatar, and set it as the new primary
|
||||
for a in avatars:
|
||||
if unicode(a.id) not in ids:
|
||||
a.primary = True
|
||||
a.save()
|
||||
avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar)
|
||||
break
|
||||
|
||||
# NOTE: `Avatar.objects.filter(id__in=ids).delete()` will NOT work
|
||||
# correctly. Sinct delete() on QuerySet will not call delete
|
||||
# method on avatar object.
|
||||
for a in Avatar.objects.filter(id__in=ids):
|
||||
a.delete()
|
||||
|
||||
# request.user.message_set.create(
|
||||
# message=_("Successfully deleted the requested avatars."))
|
||||
return HttpResponseRedirect(next_override or _get_next(request))
|
||||
return render_to_response(
|
||||
'avatar/confirm_delete.html',
|
||||
extra_context,
|
||||
context_instance = RequestContext(
|
||||
request,
|
||||
{ 'avatar': avatar,
|
||||
'avatars': avatars,
|
||||
'delete_avatar_form': delete_avatar_form,
|
||||
'next': next_override or _get_next(request), }
|
||||
)
|
||||
)
|
||||
|
||||
def render_primary(request, extra_context={}, user=None, size=AVATAR_DEFAULT_SIZE, *args, **kwargs):
|
||||
size = int(size)
|
||||
avatar = get_primary_avatar(user, size=size)
|
||||
if avatar:
|
||||
# FIXME: later, add an option to render the resized avatar dynamically
|
||||
# instead of redirecting to an already created static file. This could
|
||||
# be useful in certain situations, particulary if there is a CDN and
|
||||
# we want to minimize the storage usage on our static server, letting
|
||||
# the CDN store those files instead
|
||||
return HttpResponseRedirect(avatar.avatar_url(size))
|
||||
else:
|
||||
url = get_default_avatar_url()
|
||||
return HttpResponseRedirect(url)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load seahub_tags %}
|
||||
{% load seahub_tags avatar_tags %}
|
||||
|
||||
{% block nav_group_class %}class="cur"{% endblock %}
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
<h3>管理员</h3>
|
||||
<ul>
|
||||
{% for member in managers %}
|
||||
<li><img src="{{MEDIA_URL}}img/default-person-16.png" alt="{{ member.short_username }}的图标" class="group-member-icon" /><span class="group-member-name">{{ member.short_username }}</span></li>
|
||||
<li class="group-member">{% avatar member.user_name 16 %}<span class="group-member-name">{{ member.short_username }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h3>成员</h3>
|
||||
{% if common_members %}
|
||||
<ul>
|
||||
{% for member in common_members %}
|
||||
<li><img src="{{MEDIA_URL}}img/default-person-16.png" alt="{{ member.short_username }}的图标" class="group-member-icon" /><span class="group-member-name">{{ member.short_username }}</span></li>
|
||||
<li class="group-member">{% avatar member.user_name 16 %}<span class="group-member-name">{{ member.short_username }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 836 B |
@@ -64,6 +64,11 @@ button:hover {
|
||||
cursor:pointer;
|
||||
background: #FFF;
|
||||
}
|
||||
input[type="file"] {
|
||||
border:none;
|
||||
height:24px;
|
||||
line-height:24px;
|
||||
}
|
||||
label { display: inline-block; margin:2px 0px; }
|
||||
/* table */
|
||||
table {
|
||||
@@ -105,7 +110,18 @@ table img {
|
||||
.top-bar { height:20px; color:#fff; text-align:right; font-weight:bold; background:#606; }
|
||||
.top-bar-in { width:950px; margin:0 auto; }
|
||||
.top-bar a { color:#ddd; font-weight:normal; }
|
||||
.top-bar a.avatar-link {
|
||||
display:inline-block;
|
||||
height:16px;
|
||||
}
|
||||
.top-bar a.cur { text-decoration:underline; }
|
||||
.top-bar a,
|
||||
.top-bar span {
|
||||
vertical-align:middle;
|
||||
}
|
||||
.top-bar span {
|
||||
margin-right:3px;
|
||||
}
|
||||
.top-bar a:hover { background:#A0A; }
|
||||
/* header */
|
||||
#header .top-info { margin-bottom:5px;}
|
||||
@@ -132,9 +148,11 @@ table img {
|
||||
#main .avatar_op ul ul label { display:block; }
|
||||
#main .avatar_op li { padding:0; background:none; }
|
||||
#main .avatar_op ul ul li { float:left; margin-right:5px; }
|
||||
.avatar { float:left; width:120px; }
|
||||
.avatar {}
|
||||
.ele_info { float:right; width:450px; }
|
||||
|
||||
#upload-new-avatar-hd {
|
||||
margin-top:15px;
|
||||
}
|
||||
/*narrow-panel: for form pages*/
|
||||
.narrow-panel {
|
||||
width:25em;
|
||||
@@ -248,12 +266,12 @@ table img {
|
||||
text-decoration:none;
|
||||
}
|
||||
/* group */
|
||||
.group-member-icon {
|
||||
.group-member .avatar {
|
||||
border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.group-member-icon,
|
||||
.group-member .avatar,
|
||||
.group-member-name {
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<h3>操作</h3>
|
||||
<ul class="with-bg">
|
||||
<li><a href="{{ SITE_ROOT }}accounts/password/change/">修改网站帐号密码</a></li>
|
||||
<li><a href="{{ SITE_ROOT }}avatar/change/">修改头像</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
13
settings.py
@@ -26,7 +26,7 @@ TIME_ZONE = 'Asia/Shanghai'
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'zh-CN'
|
||||
LANGUAGE_CODE = 'zh_CN'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
@@ -99,7 +99,7 @@ INSTALLED_APPS = (
|
||||
# 'django.contrib.sites',
|
||||
# 'django.contrib.admin',
|
||||
'registration',
|
||||
# 'avatar',
|
||||
'avatar',
|
||||
'seahub.base',
|
||||
'seahub.profile',
|
||||
'seahub.contacts',
|
||||
@@ -174,10 +174,11 @@ else:
|
||||
globals()[attr] = getattr(local_settings, attr)
|
||||
|
||||
#avatar
|
||||
#AVATAR_STORAGE_DIR = 'avatars'
|
||||
|
||||
#AVATAR_GRAVATAR_BACKUP = False
|
||||
#AVATAR_DEFAULT_URL = MEDIA_URL + '/avatars/default.png'
|
||||
AVATAR_STORAGE_DIR = 'avatars'
|
||||
AVATAR_GRAVATAR_BACKUP = False
|
||||
AVATAR_DEFAULT_URL = '/avatars/default.png'
|
||||
AUTO_GENERATE_AVATAR_SIZES = (80, 16)
|
||||
AVATAR_MAX_AVATARS_PER_USER = 1
|
||||
|
||||
LOGIN_URL = SITE_ROOT + 'accounts/login'
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{% extends "accounts.html" %}
|
||||
{% block title %}个人头像{% endblock %}
|
||||
|
@@ -1,31 +0,0 @@
|
||||
{% extends "avatar/base.html" %}
|
||||
{% load avatar_tags %}
|
||||
|
||||
{% block content %}
|
||||
<div class="avatar_op">
|
||||
<h2>添加或修改头像</h2>
|
||||
<div class="pic">
|
||||
<h3>当前头像:</h3>
|
||||
{% avatar user %}
|
||||
</div>
|
||||
<div class="text">
|
||||
{% if not avatars %}
|
||||
<p>您还没有自己的头像。</p>
|
||||
{% else %}
|
||||
<h3>从已有头像中选择:</h3>
|
||||
<form method="POST" action="">
|
||||
<ul>
|
||||
{{ primary_avatar_form.as_ul }}
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
<input type="submit" value="确定" />
|
||||
</form>
|
||||
{% endif %}
|
||||
<h3>上传新头像:</h3>
|
||||
<form enctype="multipart/form-data" method="POST" action="">
|
||||
<input type="file" name="avatar" value="Avatar Image" /><br />
|
||||
<input type="submit" value="提交" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -1,18 +0,0 @@
|
||||
{% extends "avatar/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="avatar_op">
|
||||
<h2>删除头像</h2>
|
||||
{% if not avatars %}
|
||||
<p>您还没有上传自己的头像。现在 <a href="{% url avatar_change %}">上传一个</a>.</p>
|
||||
{% else %}
|
||||
<form method="POST" action="">
|
||||
<ul>
|
||||
{{ delete_avatar_form.as_ul }}
|
||||
</ul>
|
||||
<div class="clear"></div>
|
||||
<input type="submit" value="删除" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
@@ -9,6 +9,7 @@
|
||||
<link rel="icon" type="image/png" href="{{ MEDIA_URL }}img/favicon.png" />
|
||||
|
||||
{% block extra_style %}{% endblock %}
|
||||
{% load avatar_tags %}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
@@ -23,8 +24,10 @@
|
||||
<a href="{{ SITE_ROOT }}home/my/"{% block top_bar_myaccount_class %}{% endblock %}>我的帐号</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="fright">
|
||||
{% if request.user.is_authenticated %}
|
||||
欢迎, {{ request.user }}
|
||||
<span>欢迎,</span> <a href="{% url avatar_change %}" class="avatar-link">{% avatar request.user 16 %}</a> <span>{{ request.user }}</span>
|
||||
<a href="{{ SITE_ROOT }}profile/">设置</a>
|
||||
<!--
|
||||
{% if request.user.is_staff %}
|
||||
@@ -39,6 +42,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="header">
|
||||
<div class="top-info ovhd">
|
||||
<a href="{{ SITE_ROOT }}" class="fleft"><img src="{{ MEDIA_URL }}img/logo.png" title="Seafile" alt="Seafile logo" /></a>
|
||||
|
@@ -1,7 +1,9 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load avatar_tags %}
|
||||
|
||||
{% block nav_myhome_class %}class="cur"{% endblock %}
|
||||
{% block left_panel %}
|
||||
|
||||
<h3>已用空间</h3>
|
||||
<p>{{ quota_usage|filesizeformat }} / 2 GB</p>
|
||||
|
||||
|
@@ -298,11 +298,11 @@ def get_user(user_id):
|
||||
|
||||
def get_ccnetuser(username=None, userid=None):
|
||||
# Get emailuser from db
|
||||
if username != None:
|
||||
if username:
|
||||
emailuser = ccnet_rpc.get_emailuser(username)
|
||||
if userid != None:
|
||||
if userid:
|
||||
emailuser = ccnet_rpc.get_emailuser_by_id(userid)
|
||||
if emailuser == None:
|
||||
if not emailuser:
|
||||
return None
|
||||
|
||||
# And convert to ccnetuser
|
||||
|
9
urls.py
@@ -55,13 +55,14 @@ urlpatterns = patterns('',
|
||||
(r'^useradmin/(?P<user_id>[^/]+)/role/remove/$', role_remove),
|
||||
(r'^useradmin/(?P<user_id>[^/]+)/user/remove/$', user_remove),
|
||||
(r'^useradmin/activate/(?P<user_id>[^/]+)/$', activate_user),
|
||||
# (r'^avatar/', include('avatar.urls')),
|
||||
|
||||
(r'^avatar/', include('avatar.urls')),
|
||||
(r'^contacts/', include('contacts.urls')),
|
||||
(r'^group/', include('seahub.group.urls')),
|
||||
(r'^profile/', include('seahub.profile.urls')),
|
||||
(r'^back/local/$', back_local),
|
||||
|
||||
(r'^contacts/', include('contacts.urls')),
|
||||
(r'^share/', include('share.urls')),
|
||||
|
||||
(r'^back/local/$', back_local),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
|