1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-16 08:16:55 +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;
}
/* sysadmin */
#import-members-btn .icon-upload-alt,
#import-users-btn .icon-upload-alt {
color:#777;
margin-right:3px;

View File

@ -69,4 +69,10 @@ class WikiCreateForm(forms.Form):
raise forms.ValidationError(error_msg)
else:
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"])
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>
</ul>
<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-admin-add" class="hide"><img src="{{ MEDIA_URL }}img/add.png" alt="" class="add vam" /><span class="vam">{% trans "Add Admins"%}</span></button>
</div>
@ -99,6 +100,14 @@
</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 %}
<h3>{% trans "Transfer Group To"%}</h3>
<input type="hidden" name="email" /><br />
@ -132,26 +141,33 @@
<script type="text/javascript" src="{% static "scripts/lib/select2-3.5.2.js" %}"></script>
{% include "snippets/avatar_upload_js.html" %}
<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'),
import_members_btn = $('#import-members-btn'),
admin_add_btn = $('#group-admin-add');
if (cur_tab.attr('id') == 'members-tab') {
member_add_btn.removeClass('hide');
import_members_btn.removeClass('hide');
}
if (cur_tab.attr('id') == 'admin-tab') {
admin_add_btn.removeClass('hide');
}
$('#members-tab').click(function() {
member_add_btn.removeClass('hide');
import_members_btn.removeClass('hide');
admin_add_btn.addClass('hide');
});
$('#admin-tab').click(function() {
member_add_btn.addClass('hide');
import_members_btn.addClass('hide');
admin_add_btn.removeClass('hide');
});
$('#settings-tab').click(function() {
changeAvatar($('#grp-avatar-chg-btn'), $('#grp-avatar-input'), $('#grp-avatar-form'));
member_add_btn.addClass('hide');
import_members_btn.addClass('hide');
admin_add_btn.addClass('hide');
});
addConfirmTo($("#group-dismiss"), {
@ -247,5 +263,17 @@ $('#group-rename-form').submit(function() {
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>
{% 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_delete, group_wiki_use_lib, group_remove, group_dismiss, group_quit, \
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('',
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'^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'),
)

View File

@ -3,6 +3,9 @@ import logging
import os
import json
import urllib2
import csv
import chardet
import StringIO
from django.conf import settings
from django.core.paginator import EmptyPage, InvalidPage
@ -29,13 +32,14 @@ from pysearpc import SearpcError
from decorators import group_staff_required
from models import GroupMessage, MessageReply, MessageAttachment, PublicGroup
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 seahub.auth import REDIRECT_FIELD_NAME
from seahub.base.decorators import sys_staff_required, require_POST
from seahub.base.models import FileDiscuss
from seahub.contacts.models import Contact
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, \
ConflictGroupNameError
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,
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
@group_staff_required
def group_manage(request, group_id):

View File

@ -167,6 +167,23 @@ class Command(BaseCommand):
notice.avatar_src = self.get_avatar_src(username)
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):
return Profile.objects.get_user_language(username)
@ -235,6 +252,9 @@ class Command(BaseCommand):
elif notice.is_group_join_request():
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)
if not notices:

View File

@ -39,6 +39,7 @@ class NotificationForm(ModelForm):
########## user notification
MSG_TYPE_GROUP_MSG = 'group_msg'
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_FILE_UPLOADED = 'file_uploaded'
MSG_TYPE_REPO_SHARE = 'repo_share'
@ -72,7 +73,12 @@ def user_msg_to_json(message, msg_from):
def group_join_request_to_json(username, group_id, join_request_msg):
return json.dumps({'username': username, 'group_id': group_id,
'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):
def _add_user_notification(self, to_user, msg_type, detail):
"""Add generic user notification.
@ -251,7 +257,19 @@ class UserNotificationManager(models.Manager):
"""
return self._add_user_notification(to_user,
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):
"""
@ -385,6 +403,14 @@ class UserNotification(models.Model):
"""
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):
"""Parse group message detail, returns dict contains ``group_id`` and
``msg_from``.
@ -737,6 +763,34 @@ class UserNotification(models.Model):
}
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
from django.core.urlresolvers import reverse
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.group.models import GroupMessage, MessageReply
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.message.models import UserMessage
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)
def add_upload_file_msg_cb(sender, **kwargs):
"""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:
UserNotification.objects.add_group_join_request_notice(to_user=staff,
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 %}
<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 %}
</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)
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:
pass

View File

@ -45,6 +45,10 @@
{% elif notice.is_group_join_request %}
<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 %}
<p class="time">{{ notice.timestamp|translate_seahub_time }}</p>