1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-17 16:52:09 +00:00

[group] import members from csv file

This commit is contained in:
lian 2015-12-02 14:10:32 +08:00
parent 53ba4f2fd6
commit 577c27c9d2
11 changed files with 298 additions and 7 deletions

View File

@ -3577,6 +3577,7 @@ textarea:-moz-placeholder {/* for FF */
vertical-align: middle; vertical-align: middle;
} }
/* sysadmin */ /* sysadmin */
#import-members-btn .icon-upload-alt,
#import-users-btn .icon-upload-alt { #import-users-btn .icon-upload-alt {
color:#777; color:#777;
margin-right:3px; margin-right:3px;

View File

@ -70,3 +70,9 @@ class WikiCreateForm(forms.Form):
else: else:
return repo_name return repo_name
class BatchAddMembersForm(forms.Form):
"""
Form for importing group members from CSV file.
"""
file = forms.FileField()

View File

@ -4,3 +4,4 @@ grpmsg_added = django.dispatch.Signal(providing_args=["group_id", "from_email",
grpmsg_reply_added = django.dispatch.Signal(providing_args=["msg_id", "from_email", "grpmsg_topic", "reply_msg"]) grpmsg_reply_added = django.dispatch.Signal(providing_args=["msg_id", "from_email", "grpmsg_topic", "reply_msg"])
group_join_request = django.dispatch.Signal(providing_args=["staffs", "username", "group", "join_reqeust_msg"]) group_join_request = django.dispatch.Signal(providing_args=["staffs", "username", "group", "join_reqeust_msg"])
add_user_to_group = django.dispatch.Signal(providing_args=["group_staff", "group_id", "added_user"])

View File

@ -17,6 +17,7 @@
<li class="tab"><a href="#settings" class="a" id="settings-tab">{% trans "Settings" %}</a></li> <li class="tab"><a href="#settings" class="a" id="settings-tab">{% trans "Settings" %}</a></li>
</ul> </ul>
<div class="fright"> <div class="fright">
<button id="import-members-btn" class="hide"><span class="icon-upload-alt"></span>{% trans "Import Members" %}</button>
<button id="group-member-add" class="hide"><img src="{{ MEDIA_URL }}img/add.png" alt="" class="add vam" /><span class="vam">{% trans "Add Members"%}</span></button> <button id="group-member-add" class="hide"><img src="{{ MEDIA_URL }}img/add.png" alt="" class="add vam" /><span class="vam">{% trans "Add Members"%}</span></button>
<button id="group-admin-add" class="hide"><img src="{{ MEDIA_URL }}img/add.png" alt="" class="add vam" /><span class="vam">{% trans "Add Admins"%}</span></button> <button id="group-admin-add" class="hide"><img src="{{ MEDIA_URL }}img/add.png" alt="" class="add vam" /><span class="vam">{% trans "Add Admins"%}</span></button>
</div> </div>
@ -99,6 +100,14 @@
</div> </div>
<form id="upload-csv-form" class="hide" enctype="multipart/form-data" method="post" action="{% url 'batch_add_members' group.id %}">{% csrf_token %}
<h3>{% trans "Import group members from a CSV file" %}</h3>
<input type="file" name="file" />
<p class="tip">{% trans "File format: user@mail.com"%}</p>
<p class="error hide">{% trans "Please choose a CSV file" %}</p>
<button type="submit" class="submit">{% trans "Submit" %}</button>
</form>
<form id="group-transfer-form" method="post" action="{% url 'group_transfer' group.id %}" class="hide">{% csrf_token %} <form id="group-transfer-form" method="post" action="{% url 'group_transfer' group.id %}" class="hide">{% csrf_token %}
<h3>{% trans "Transfer Group To"%}</h3> <h3>{% trans "Transfer Group To"%}</h3>
<input type="hidden" name="email" /><br /> <input type="hidden" name="email" /><br />
@ -134,24 +143,31 @@
<script type="text/javascript"> <script type="text/javascript">
var cur_tab = $('.ui-tabs-selected .a'); var cur_tab = $('.ui-tabs-selected .a');
var member_add_btn = $('#group-member-add'), var member_add_btn = $('#group-member-add'),
import_members_btn = $('#import-members-btn'),
admin_add_btn = $('#group-admin-add'); admin_add_btn = $('#group-admin-add');
if (cur_tab.attr('id') == 'members-tab') { if (cur_tab.attr('id') == 'members-tab') {
member_add_btn.removeClass('hide'); member_add_btn.removeClass('hide');
import_members_btn.removeClass('hide');
} }
if (cur_tab.attr('id') == 'admin-tab') { if (cur_tab.attr('id') == 'admin-tab') {
admin_add_btn.removeClass('hide'); admin_add_btn.removeClass('hide');
} }
$('#members-tab').click(function() { $('#members-tab').click(function() {
member_add_btn.removeClass('hide'); member_add_btn.removeClass('hide');
import_members_btn.removeClass('hide');
admin_add_btn.addClass('hide'); admin_add_btn.addClass('hide');
}); });
$('#admin-tab').click(function() { $('#admin-tab').click(function() {
member_add_btn.addClass('hide'); member_add_btn.addClass('hide');
import_members_btn.addClass('hide');
admin_add_btn.removeClass('hide'); admin_add_btn.removeClass('hide');
}); });
$('#settings-tab').click(function() { $('#settings-tab').click(function() {
changeAvatar($('#grp-avatar-chg-btn'), $('#grp-avatar-input'), $('#grp-avatar-form')); changeAvatar($('#grp-avatar-chg-btn'), $('#grp-avatar-input'), $('#grp-avatar-form'));
member_add_btn.addClass('hide'); member_add_btn.addClass('hide');
import_members_btn.addClass('hide');
admin_add_btn.addClass('hide'); admin_add_btn.addClass('hide');
}); });
addConfirmTo($("#group-dismiss"), { addConfirmTo($("#group-dismiss"), {
@ -247,5 +263,17 @@ $('#group-rename-form').submit(function() {
return false; return false;
} }
}); });
$('#import-members-btn').click(function () {
$('#upload-csv-form').modal();
$('#simplemodal-container').css({'width':'auto', 'height':'auto'});
});
$('#upload-csv-form').submit(function() {
var form = $(this);
if (!$('[name=file]', form).val()) {
$('.error', form).removeClass('hide');
return false;
}
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -7,7 +7,8 @@ from views import group_info, group_members, group_member_operations, group_add_
group_wiki_page_new, group_wiki_page_edit, group_wiki_pages, \ group_wiki_page_new, group_wiki_page_edit, group_wiki_pages, \
group_wiki_page_delete, group_wiki_use_lib, group_remove, group_dismiss, group_quit, \ group_wiki_page_delete, group_wiki_use_lib, group_remove, group_dismiss, group_quit, \
group_make_public, group_revoke_public, group_transfer, group_toggle_modules, \ group_make_public, group_revoke_public, group_transfer, group_toggle_modules, \
group_add_discussion, group_rename, group_add, ajax_add_group_member group_add_discussion, group_rename, group_add, ajax_add_group_member, \
batch_add_members
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^(?P<group_id>\d+)/$', group_info, name='group_info'), url(r'^(?P<group_id>\d+)/$', group_info, name='group_info'),
@ -42,6 +43,7 @@ urlpatterns = patterns('',
url(r'^(?P<group_id>\d+)/discussion/add/$', group_add_discussion, name='group_add_discussion'), url(r'^(?P<group_id>\d+)/discussion/add/$', group_add_discussion, name='group_add_discussion'),
url(r'^add/$', group_add, name='group_add'), url(r'^add/$', group_add, name='group_add'),
url(r'^(?P<group_id>\d+)/batch-add-members/$', batch_add_members, name='batch_add_members'),
url(r'^ajax/(?P<group_id>\d+)/member/add/$', ajax_add_group_member, name='group_add_member'), url(r'^ajax/(?P<group_id>\d+)/member/add/$', ajax_add_group_member, name='group_add_member'),
) )

View File

@ -3,6 +3,9 @@ import logging
import os import os
import json import json
import urllib2 import urllib2
import csv
import chardet
import StringIO
from django.conf import settings from django.conf import settings
from django.core.paginator import EmptyPage, InvalidPage from django.core.paginator import EmptyPage, InvalidPage
@ -29,13 +32,14 @@ from pysearpc import SearpcError
from decorators import group_staff_required from decorators import group_staff_required
from models import GroupMessage, MessageReply, MessageAttachment, PublicGroup from models import GroupMessage, MessageReply, MessageAttachment, PublicGroup
from forms import MessageForm, MessageReplyForm, GroupRecommendForm, \ from forms import MessageForm, MessageReplyForm, GroupRecommendForm, \
GroupAddForm, GroupJoinMsgForm, WikiCreateForm GroupAddForm, GroupJoinMsgForm, WikiCreateForm, BatchAddMembersForm
from signals import grpmsg_added, grpmsg_reply_added, group_join_request from signals import grpmsg_added, grpmsg_reply_added, group_join_request
from seahub.auth import REDIRECT_FIELD_NAME from seahub.auth import REDIRECT_FIELD_NAME
from seahub.base.decorators import sys_staff_required, require_POST from seahub.base.decorators import sys_staff_required, require_POST
from seahub.base.models import FileDiscuss from seahub.base.models import FileDiscuss
from seahub.contacts.models import Contact from seahub.contacts.models import Contact
from seahub.contacts.signals import mail_sended from seahub.contacts.signals import mail_sended
from seahub.group.signals import add_user_to_group
from seahub.group.utils import validate_group_name, BadGroupNameError, \ from seahub.group.utils import validate_group_name, BadGroupNameError, \
ConflictGroupNameError ConflictGroupNameError
from seahub.notifications.models import UserNotification from seahub.notifications.models import UserNotification
@ -756,6 +760,136 @@ def ajax_add_group_member(request, group_id):
return HttpResponse(json.dumps('success'), status=200, return HttpResponse(json.dumps('success'), status=200,
content_type=content_type) content_type=content_type)
@login_required
@group_staff_required
def batch_add_members(request, group_id):
"""Batch add users to group.
"""
group = get_group(group_id)
referer = request.META.get('HTTP_REFERER', None)
if not group:
next = SITE_ROOT if referer is None else referer
messages.error(request, _(u'The group does not exist.'))
return HttpResponseRedirect(next)
next = reverse('group_manage', args=[group_id]) \
if referer is None else referer
reader = []
form = BatchAddMembersForm(request.POST, request.FILES)
if form.is_valid():
uploaded_file = request.FILES['file']
if uploaded_file.size > 10 * 1024 * 1024:
messages.error(request, _(u'Failed, file is too large'))
return HttpResponseRedirect(next)
try:
content = uploaded_file.read()
encoding = chardet.detect(content)['encoding']
if encoding != 'utf-8':
content = content.decode(encoding, 'replace').encode('utf-8')
filestream = StringIO.StringIO(content)
reader = csv.reader(filestream)
except Exception as e:
logger.error(e)
messages.error(request, _(u'Failed'))
return HttpResponseRedirect(next)
failed = []
success = []
member_list = []
for row in reader:
if not row:
continue
email = row[0].strip().lower()
if not is_valid_username(email):
failed.append(email)
continue
if is_group_user(group.id, email):
continue
member_list.append(email)
username = request.user.username
if is_org_context(request):
# Can only invite organization users to group
org_id = request.user.org.org_id
for email in member_list:
if not org_user_exists(org_id, email):
failed.append(email)
continue
# Add user to group.
try:
ccnet_threaded_rpc.group_add_member(group.id,
username,
email)
success.append(email)
except SearpcError, e:
failed.append(email)
logger.error(e)
continue
elif request.cloud_mode:
# check plan
group_members = getattr(request.user, 'group_members', -1)
if group_members > 0:
current_group_members = len(get_group_members(group.id))
if current_group_members > group_members:
messages.error(request, _(u'You can only invite %d members.<a href="http://seafile.com/">Upgrade account.</a>') % group_members)
return HttpResponseRedirect(next)
# Can invite unregistered user to group.
for email in member_list:
# Add user to group, unregistered user will see the group
# when he logs in.
try:
ccnet_threaded_rpc.group_add_member(group.id,
username,
email)
success.append(email)
except SearpcError as e:
failed.append(email)
logger.error(e)
continue
else:
# Can only invite registered user to group if not in cloud mode.
for email in member_list:
if not is_registered_user(email):
failed.append(email)
continue
# Add user to group.
try:
ccnet_threaded_rpc.group_add_member(group.id,
username,
email)
success.append(email)
except SearpcError, e:
failed.append(email)
logger.error(e)
continue
for email in success:
add_user_to_group.send(sender = None,
group_staff = username,
group_id = group_id,
added_user = email)
for email in failed:
messages.error(request, _(u'Failed to add %s to group.') % email)
return HttpResponseRedirect(next)
@login_required @login_required
@group_staff_required @group_staff_required
def group_manage(request, group_id): def group_manage(request, group_id):

View File

@ -167,6 +167,23 @@ class Command(BaseCommand):
notice.avatar_src = self.get_avatar_src(username) notice.avatar_src = self.get_avatar_src(username)
return notice return notice
def format_add_user_to_group(self, notice):
d = json.loads(notice.detail)
group_staff = d['group_staff']
group_id = d['group_id']
group = seaserv.get_group(group_id)
if group is None:
notice.delete()
notice.notice_from = group_staff
notice.avatar_src = self.get_avatar_src(group_staff)
notice.group_staff_profile_url = reverse('user_profile',
args=[group_staff])
notice.group_url = reverse('view_group', args=[group_id])
notice.group_name = group.group_name
return notice
def get_user_language(self, username): def get_user_language(self, username):
return Profile.objects.get_user_language(username) return Profile.objects.get_user_language(username)
@ -235,6 +252,9 @@ class Command(BaseCommand):
elif notice.is_group_join_request(): elif notice.is_group_join_request():
notice = self.format_group_join_request(notice) notice = self.format_group_join_request(notice)
elif notice.is_add_user_to_group():
notice = self.format_add_user_to_group(notice)
notices.append(notice) notices.append(notice)
if not notices: if not notices:

View File

@ -39,6 +39,7 @@ class NotificationForm(ModelForm):
########## user notification ########## user notification
MSG_TYPE_GROUP_MSG = 'group_msg' MSG_TYPE_GROUP_MSG = 'group_msg'
MSG_TYPE_GROUP_JOIN_REQUEST = 'group_join_request' MSG_TYPE_GROUP_JOIN_REQUEST = 'group_join_request'
MSG_TYPE_ADD_USER_TO_GROUP = 'add_user_to_group'
MSG_TYPE_GRPMSG_REPLY = 'grpmsg_reply' MSG_TYPE_GRPMSG_REPLY = 'grpmsg_reply'
MSG_TYPE_FILE_UPLOADED = 'file_uploaded' MSG_TYPE_FILE_UPLOADED = 'file_uploaded'
MSG_TYPE_REPO_SHARE = 'repo_share' MSG_TYPE_REPO_SHARE = 'repo_share'
@ -73,6 +74,11 @@ def group_join_request_to_json(username, group_id, join_request_msg):
return json.dumps({'username': username, 'group_id': group_id, return json.dumps({'username': username, 'group_id': group_id,
'join_request_msg': join_request_msg}) 'join_request_msg': join_request_msg})
def add_user_to_group_to_json(group_staff, group_id):
return json.dumps({'group_staff': group_staff,
'group_id': group_id})
class UserNotificationManager(models.Manager): class UserNotificationManager(models.Manager):
def _add_user_notification(self, to_user, msg_type, detail): def _add_user_notification(self, to_user, msg_type, detail):
"""Add generic user notification. """Add generic user notification.
@ -252,6 +258,18 @@ class UserNotificationManager(models.Manager):
return self._add_user_notification(to_user, return self._add_user_notification(to_user,
MSG_TYPE_GROUP_JOIN_REQUEST, detail) MSG_TYPE_GROUP_JOIN_REQUEST, detail)
def set_add_user_to_group_notice(self, to_user, detail):
"""
Arguments:
- `self`:
- `to_user`:
- `detail`:
"""
return self._add_user_notification(to_user,
MSG_TYPE_ADD_USER_TO_GROUP,
detail)
def add_file_uploaded_msg(self, to_user, detail): def add_file_uploaded_msg(self, to_user, detail):
""" """
@ -385,6 +403,14 @@ class UserNotification(models.Model):
""" """
return self.msg_type == MSG_TYPE_GROUP_JOIN_REQUEST return self.msg_type == MSG_TYPE_GROUP_JOIN_REQUEST
def is_add_user_to_group(self):
"""
Arguments:
- `self`:
"""
return self.msg_type == MSG_TYPE_ADD_USER_TO_GROUP
def group_message_detail_to_dict(self): def group_message_detail_to_dict(self):
"""Parse group message detail, returns dict contains ``group_id`` and """Parse group message detail, returns dict contains ``group_id`` and
``msg_from``. ``msg_from``.
@ -737,6 +763,34 @@ class UserNotification(models.Model):
} }
return msg return msg
def format_add_user_to_group(self):
"""
Arguments:
- `self`:
"""
try:
d = json.loads(self.detail)
except Exception as e:
logger.error(e)
return _(u"Internal error")
group_staff = d['group_staff']
group_id = d['group_id']
group = seaserv.get_group(group_id)
if group is None:
self.delete()
return None
msg = _(u"User <a href='%(user_profile)s'>%(group_staff)s</a> has added you to group <a href='%(href)s'>%(group_name)s</a>") % {
'user_profile': reverse('user_profile', args=[group_staff]),
'group_staff': group_staff,
'href': reverse('view_group', args=[group_id]),
'group_name': group.group_name,
}
return msg
########## handle signals ########## handle signals
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.dispatch import receiver from django.dispatch import receiver
@ -744,11 +798,28 @@ from django.dispatch import receiver
from seahub.signals import share_file_to_user_successful, upload_file_successful from seahub.signals import share_file_to_user_successful, upload_file_successful
from seahub.group.models import GroupMessage, MessageReply from seahub.group.models import GroupMessage, MessageReply
from seahub.group.signals import grpmsg_added, grpmsg_reply_added, \ from seahub.group.signals import grpmsg_added, grpmsg_reply_added, \
group_join_request group_join_request, add_user_to_group
from seahub.share.signals import share_repo_to_user_successful from seahub.share.signals import share_repo_to_user_successful
from seahub.message.models import UserMessage from seahub.message.models import UserMessage
from seahub.message.signals import user_message_sent from seahub.message.signals import user_message_sent
@receiver(upload_file_successful)
def add_upload_file_msg_cb(sender, **kwargs):
"""Notify repo owner when others upload files to his/her folder from shared link.
"""
repo_id = kwargs.get('repo_id', None)
file_path = kwargs.get('file_path', None)
owner = kwargs.get('owner', None)
assert repo_id and file_path and owner is not None, 'Arguments error'
filename = os.path.basename(file_path)
folder_path = os.path.dirname(file_path)
folder_name = os.path.basename(folder_path)
detail = file_uploaded_msg_to_json(filename, repo_id, folder_path)
UserNotification.objects.add_file_uploaded_msg(owner, detail)
@receiver(upload_file_successful) @receiver(upload_file_successful)
def add_upload_file_msg_cb(sender, **kwargs): def add_upload_file_msg_cb(sender, **kwargs):
"""Notify repo owner when others upload files to his/her folder from shared link. """Notify repo owner when others upload files to his/her folder from shared link.
@ -857,3 +928,15 @@ def group_join_request_cb(sender, **kwargs):
for staff in staffs: for staff in staffs:
UserNotification.objects.add_group_join_request_notice(to_user=staff, UserNotification.objects.add_group_join_request_notice(to_user=staff,
detail=detail) detail=detail)
@receiver(add_user_to_group)
def add_user_to_group_cb(sender, **kwargs):
group_staff = kwargs['group_staff']
group_id = kwargs['group_id']
added_user = kwargs['added_user']
detail = add_user_to_group_to_json(group_staff,
group_id)
UserNotification.objects.set_add_user_to_group_notice(to_user=added_user,
detail=detail)

View File

@ -57,6 +57,10 @@ You've got {{num}} new notices on {{ site_name }}:
{% elif notice.is_group_join_request %} {% elif notice.is_group_join_request %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">{% blocktrans with user_url=notice.grpjoin_user_profile_url user=notice.notice_from grp_url=notice.grpjoin_group_url grp_name=notice.grpjoin_group_name msg=notice.grpjoin_request_msg %}User <a href="{{url_base}}{{user_url}}">{{user}}</a> has asked to join group <a href="{{url_base}}{{grp_url}}">{{grp_name}}</a>, verification message: {{msg}}{% endblocktrans %}</p> <p style="line-height:1.5; margin:.2em 10px .2em 0;">{% blocktrans with user_url=notice.grpjoin_user_profile_url user=notice.notice_from grp_url=notice.grpjoin_group_url grp_name=notice.grpjoin_group_name msg=notice.grpjoin_request_msg %}User <a href="{{url_base}}{{user_url}}">{{user}}</a> has asked to join group <a href="{{url_base}}{{grp_url}}">{{grp_name}}</a>, verification message: {{msg}}{% endblocktrans %}</p>
{% elif notice.is_add_user_to_group %}
<p style="line-height:1.5; margin:.2em 10px .2em 0;">{% blocktrans with user_url=notice.group_staff_profile_url user=notice.notice_from grp_url=notice.group_url grp_name=notice.group_name %}User <a href="{{url_base}}{{user_url}}">{{user}}</a> has added you to group <a href="{{url_base}}{{grp_url}}">{{grp_name}}</a>{% endblocktrans %}</p>
{% endif %} {% endif %}
</td> </td>
<td style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size: 13px; color: #333; word-wrap: break-word;">{{ notice.timestamp|date:"Y-m-d G:i:s"}}</td> <td style="padding: 5px 3px; border-bottom: 1px solid #eee; font-size: 13px; color: #333; word-wrap: break-word;">{{ notice.timestamp|date:"Y-m-d G:i:s"}}</td>

View File

@ -178,6 +178,14 @@ def add_notice_from_info(notices):
logger.error(e) logger.error(e)
notice.default_avatar_url = default_avatar_url notice.default_avatar_url = default_avatar_url
elif notice.is_add_user_to_group():
try:
d = json.loads(notice.detail)
notice.msg_from = d['group_staff']
except Exception as e:
logger.error(e)
notice.default_avatar_url = default_avatar_url
else: else:
pass pass

View File

@ -45,6 +45,10 @@
{% elif notice.is_group_join_request %} {% elif notice.is_group_join_request %}
<p class="brief">{{ notice.format_group_join_request|safe }}</p> <p class="brief">{{ notice.format_group_join_request|safe }}</p>
{% elif notice.is_add_user_to_group %}
<p class="brief">{{ notice.format_add_user_to_group|safe }}</p>
{% endif %} {% endif %}
<p class="time">{{ notice.timestamp|translate_seahub_time }}</p> <p class="time">{{ notice.timestamp|translate_seahub_time }}</p>