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:
parent
53ba4f2fd6
commit
577c27c9d2
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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"])
|
||||
|
@ -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 %}
|
||||
|
@ -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'),
|
||||
)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user