mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-28 03:10:45 +00:00
added func: set group avatar
This commit is contained in:
parent
cbab401d84
commit
75b724a169
@ -1,8 +1,30 @@
|
||||
import os
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
|
||||
from group.settings import ( AVATAR_MAX_SIZE, AVATAR_ALLOWED_FILE_EXTS)
|
||||
|
||||
class MessageForm(forms.Form):
|
||||
message = forms.CharField(max_length=500)
|
||||
|
||||
class MessageReplyForm(forms.Form):
|
||||
message = forms.CharField(max_length=150)
|
||||
|
||||
|
||||
class AvatarForm(forms.Form):
|
||||
avatar = forms.ImageField()
|
||||
|
||||
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)})
|
||||
|
||||
|
104
group/models.py
104
group/models.py
@ -1,5 +1,26 @@
|
||||
import datetime
|
||||
import os
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
|
||||
from django.db import models
|
||||
from group.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)
|
||||
|
||||
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
|
||||
|
||||
class GroupMessage(models.Model):
|
||||
group_id = models.IntegerField()
|
||||
@ -12,3 +33,86 @@ class MessageReply(models.Model):
|
||||
from_email = models.EmailField()
|
||||
message = models.CharField(max_length=150)
|
||||
timestamp = models.DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
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.group_id])
|
||||
else:
|
||||
tmppath.append(instance.group_id)
|
||||
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):
|
||||
group_id = models.CharField(max_length=255)
|
||||
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.group_id
|
||||
|
||||
def thumbnail_exists(self, size):
|
||||
return self.avatar.storage.exists(self.avatar_name(size))
|
||||
|
||||
def create_thumbnail(self, size, quality=None):
|
||||
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
|
||||
)
|
||||
|
23
group/settings.py
Normal file
23
group/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 = 48
|
||||
AUTO_GENERATE_AVATAR_SIZES = (80, 48)
|
||||
AVATAR_RESIZE_METHOD = getattr(settings, 'AVATAR_RESIZE_METHOD', Image.ANTIALIAS)
|
||||
AVATAR_STORAGE_DIR = 'avatars/groups'
|
||||
AVATAR_GRAVATAR_BACKUP = getattr(settings, 'AVATAR_GRAVATAR_BACKUP', True)
|
||||
AVATAR_GRAVATAR_DEFAULT = getattr(settings, 'AVATAR_GRAVATAR_DEFAULT', None)
|
||||
AVATAR_DEFAULT_URL = 'avatars/groups/default.png'
|
||||
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)
|
@ -10,8 +10,9 @@
|
||||
<div class="side fright">
|
||||
<h3>操作</h3>
|
||||
<ul class="with-bg">
|
||||
<li><a href="{{ SITE_ROOT }}group/{{ group.id }}/set_avatar/">设置小组图标</a></li>
|
||||
<li><a id="group-remove" href="#" data="{{ SITE_ROOT }}group/{{ group.id }}/?op=delete">解散小组</a></li>
|
||||
<li><a id="group-info" href="{{ SITE_ROOT }}group/{{ group.id }}/">返回小组</a></li>
|
||||
<li><a href="{{ SITE_ROOT }}group/{{ group.id }}/">返回小组</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load seahub_tags %}
|
||||
{% load seahub_tags group_tags %}
|
||||
|
||||
{% block nav_group_class %}class="cur"{% endblock %}
|
||||
{% block left_panel %}
|
||||
@ -18,7 +18,7 @@
|
||||
<li class="group fleft">
|
||||
<div class="pic fleft">
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/">
|
||||
<img src="{{ MEDIA_URL }}img/group.jpg" alt="小组图标" />
|
||||
<img src="{% grp_avatar_url group.props.id 48 %}" alt="{{ group.props.group_name }}的图标" title="{{ group.props.group_name }}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="txt fright">
|
||||
@ -44,7 +44,7 @@
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
$("#group-add").click(function() {
|
||||
$("#group-add-form").modal({appendTo: "#main", containerCss:{padding:18}});
|
||||
$("#group-add-form").modal({appendTo: "#main"});
|
||||
return false;
|
||||
});
|
||||
|
||||
|
21
group/templates/group/set_avatar.html
Normal file
21
group/templates/group/set_avatar.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load group_tags %}
|
||||
|
||||
{% block nav_group_class %}class="cur"{% endblock %}
|
||||
|
||||
{% block main_panel %}
|
||||
<div class="avatar-op">
|
||||
<h2>修改小组 {{ group.group_name }} 的图标</h2>
|
||||
<div class="avatar-op-con">
|
||||
<h3>当前图标:</h3>
|
||||
<img src="{% grp_avatar_url group.id %}" alt="当前图标" class="avatar" />
|
||||
|
||||
<h3 class="upload-new-avatar-hd">上传新图标:</h3>
|
||||
<form enctype="multipart/form-data" method="post" action="">
|
||||
{{ form.avatar }}<br />
|
||||
{{ form.avatar.errors }}
|
||||
<input type="submit" value="提交" class="submit" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
0
group/templatetags/__init__.py
Normal file
0
group/templatetags/__init__.py
Normal file
45
group/templatetags/group_tags.py
Normal file
45
group/templatetags/group_tags.py
Normal file
@ -0,0 +1,45 @@
|
||||
import urllib
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from django import template
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from group.settings import (AVATAR_DEFAULT_SIZE, AVATAR_DEFAULT_URL)
|
||||
from group.models import Avatar
|
||||
|
||||
register = template.Library()
|
||||
|
||||
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)
|
||||
|
||||
@register.simple_tag
|
||||
def grp_avatar_url(group_id, size=AVATAR_DEFAULT_SIZE):
|
||||
grp_avatars = Avatar.objects.filter(group_id=group_id)
|
||||
if grp_avatars:
|
||||
avatar = grp_avatars.order_by('-date_uploaded')[0]
|
||||
else:
|
||||
avatar = None
|
||||
|
||||
if avatar:
|
||||
if not avatar.thumbnail_exists(size):
|
||||
avatar.create_thumbnail(size)
|
||||
avatar_src = avatar.avatar_url(size)
|
||||
else:
|
||||
avatar_src = get_default_avatar_url()
|
||||
|
||||
return avatar_src
|
@ -1,12 +1,13 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
from views import group_list, group_info, group_member_operations, \
|
||||
group_members, msg_reply, msg_reply_new
|
||||
group_members, msg_reply, msg_reply_new, set_avatar
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<group_id>[\d]+)/$', group_info, name='group_info'),
|
||||
url(r'^reply/(?P<msg_id>[\d]+)/$', msg_reply, name='msg_reply'),
|
||||
url(r'^reply/new/$', msg_reply_new, name='msg_reply_new'),
|
||||
url(r'^(?P<group_id>[\d]+)/set_avatar/$', set_avatar, name='set_avatar'),
|
||||
url(r'^(?P<group_id>[\d]+)/members/$', group_members, name='group_members'),
|
||||
(r'^(?P<group_id>[\d]+)/member/(?P<user_name>[^/]+)/$', group_member_operations),
|
||||
)
|
||||
|
@ -10,8 +10,8 @@ from seaserv import ccnet_rpc, ccnet_threaded_rpc, seafserv_threaded_rpc, get_re
|
||||
get_group_repoids, check_group_staff, get_commits
|
||||
from pysearpc import SearpcError
|
||||
|
||||
from models import GroupMessage, MessageReply
|
||||
from forms import MessageForm, MessageReplyForm
|
||||
from models import GroupMessage, MessageReply, Avatar
|
||||
from forms import MessageForm, MessageReplyForm, AvatarForm
|
||||
from signals import grpmsg_added, grpmsg_reply_added
|
||||
from seahub.contacts.models import Contact
|
||||
from seahub.notifications.models import UserNotification
|
||||
@ -457,3 +457,32 @@ def group_unshare_repo(request, repo_id, group_id, from_email):
|
||||
|
||||
if seafserv_threaded_rpc.group_unshare_repo(repo_id, group_id, from_email) != 0:
|
||||
return go_error(request, u'共享失败:内部错误')
|
||||
|
||||
@login_required
|
||||
def set_avatar(request, group_id):
|
||||
try:
|
||||
group_id_int = int(group_id)
|
||||
except ValueError:
|
||||
return go_error(request, u'group id 不是有效参数')
|
||||
|
||||
if not check_group_staff(group_id_int, request.user):
|
||||
return go_permission_error(request, u'只有小组管理员有权设置小组图标')
|
||||
|
||||
group = ccnet_threaded_rpc.get_group(group_id_int)
|
||||
if not group:
|
||||
return HttpResponseRedirect(reverse('group_list', args=[]))
|
||||
|
||||
form = AvatarForm(request.POST or None, request.FILES or None)
|
||||
|
||||
if request.method == 'POST' and 'avatar' in request.FILES:
|
||||
if form.is_valid():
|
||||
image_file = request.FILES['avatar']
|
||||
avatar = Avatar()
|
||||
avatar.avatar.save(image_file.name, image_file)
|
||||
avatar.group_id = group_id
|
||||
avatar.save()
|
||||
|
||||
return render_to_response('group/set_avatar.html', {
|
||||
'group' : group,
|
||||
'form' : form,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 751 B |
BIN
media/avatars/groups/default.png
Normal file
BIN
media/avatars/groups/default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB |
@ -1,5 +1,5 @@
|
||||
{% extends "myhome_base.html" %}
|
||||
{% load seahub_tags avatar_tags %}
|
||||
{% load seahub_tags avatar_tags group_tags %}
|
||||
|
||||
{% block nav_myhome_class %}class="cur"{% endblock %}
|
||||
{% block left_panel %}
|
||||
@ -31,7 +31,9 @@
|
||||
<ul>
|
||||
{% for group in groups_manage %}
|
||||
<li class="mygroup">
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/"><img src="{{ MEDIA_URL }}img/group.jpg" alt="" /></a><br />
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/">
|
||||
<img src="{% grp_avatar_url group.props.id 48 %}" alt="{{ group.props.group_name }}的图标" title="{{ group.props.group_name }}" />
|
||||
</a><br />
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/">{{ group.props.group_name }}</a>
|
||||
{% if group.new_msg %}
|
||||
<span class="tips">(新留言)</span>
|
||||
@ -48,7 +50,9 @@
|
||||
<ul>
|
||||
{% for group in groups_join %}
|
||||
<li class="mygroup">
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/"><img src="{{ MEDIA_URL }}img/group.jpg" alt="" /></a><br />
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/">
|
||||
<img src="{% grp_avatar_url group.props.id 48 %}" alt="{{ group.props.group_name }}的图标" title="{{ group.props.group_name }}" />
|
||||
</a><br />
|
||||
<a href="{{ SITE_ROOT }}group/{{ group.props.id }}/">{{ group.props.group_name }}</a>
|
||||
{% if group.new_msg %}
|
||||
<span class="tips">(新留言)</span>
|
||||
|
Loading…
Reference in New Issue
Block a user