diff --git a/media/css/seahub.css b/media/css/seahub.css index 19c735d5f0..687758b996 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -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; diff --git a/seahub/group/forms.py b/seahub/group/forms.py index f9e6d0c965..d0c5699055 100644 --- a/seahub/group/forms.py +++ b/seahub/group/forms.py @@ -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() diff --git a/seahub/group/signals.py b/seahub/group/signals.py index c7a28f6c65..94ad173e6a 100644 --- a/seahub/group/signals.py +++ b/seahub/group/signals.py @@ -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"]) diff --git a/seahub/group/templates/group/group_manage.html b/seahub/group/templates/group/group_manage.html index ef8911591a..9f2b3369bc 100644 --- a/seahub/group/templates/group/group_manage.html +++ b/seahub/group/templates/group/group_manage.html @@ -17,6 +17,7 @@
  • {% trans "Settings" %}
  • +
    @@ -99,6 +100,14 @@ +
    {% csrf_token %} +

    {% trans "Import group members from a CSV file" %}

    + +

    {% trans "File format: user@mail.com"%}

    +

    {% trans "Please choose a CSV file" %}

    + +
    +
    {% csrf_token %}

    {% trans "Transfer Group To"%}


    @@ -132,26 +141,33 @@ {% include "snippets/avatar_upload_js.html" %} {% endblock %} diff --git a/seahub/group/urls.py b/seahub/group/urls.py index e3c4ed47f2..0ab1b56dc0 100644 --- a/seahub/group/urls.py +++ b/seahub/group/urls.py @@ -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\d+)/$', group_info, name='group_info'), @@ -42,6 +43,7 @@ urlpatterns = patterns('', url(r'^(?P\d+)/discussion/add/$', group_add_discussion, name='group_add_discussion'), url(r'^add/$', group_add, name='group_add'), + url(r'^(?P\d+)/batch-add-members/$', batch_add_members, name='batch_add_members'), url(r'^ajax/(?P\d+)/member/add/$', ajax_add_group_member, name='group_add_member'), ) diff --git a/seahub/group/views.py b/seahub/group/views.py index d92e453eb0..4fe088d06d 100644 --- a/seahub/group/views.py +++ b/seahub/group/views.py @@ -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.Upgrade account.') % 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): diff --git a/seahub/notifications/management/commands/send_notices.py b/seahub/notifications/management/commands/send_notices.py index 143914b60f..94980cda8c 100644 --- a/seahub/notifications/management/commands/send_notices.py +++ b/seahub/notifications/management/commands/send_notices.py @@ -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: diff --git a/seahub/notifications/models.py b/seahub/notifications/models.py index b578eb88d7..fde93762ac 100644 --- a/seahub/notifications/models.py +++ b/seahub/notifications/models.py @@ -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 %(group_staff)s has added you to group %(group_name)s") % { + '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) diff --git a/seahub/notifications/templates/notifications/notice_email.html b/seahub/notifications/templates/notifications/notice_email.html index 0ac8cac05e..b064890db4 100644 --- a/seahub/notifications/templates/notifications/notice_email.html +++ b/seahub/notifications/templates/notifications/notice_email.html @@ -57,6 +57,10 @@ You've got {{num}} new notices on {{ site_name }}: {% elif notice.is_group_join_request %}

    {% 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 {{user}} has asked to join group {{grp_name}}, verification message: {{msg}}{% endblocktrans %}

    + + {% elif notice.is_add_user_to_group %} +

    {% blocktrans with user_url=notice.group_staff_profile_url user=notice.notice_from grp_url=notice.group_url grp_name=notice.group_name %}User {{user}} has added you to group {{grp_name}}{% endblocktrans %}

    + {% endif %} {{ notice.timestamp|date:"Y-m-d G:i:s"}} diff --git a/seahub/notifications/views.py b/seahub/notifications/views.py index 409b89c921..f37f6a5cde 100644 --- a/seahub/notifications/views.py +++ b/seahub/notifications/views.py @@ -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 diff --git a/seahub/templates/snippets/notice_html.html b/seahub/templates/snippets/notice_html.html index 05097a6182..b834c64099 100644 --- a/seahub/templates/snippets/notice_html.html +++ b/seahub/templates/snippets/notice_html.html @@ -45,6 +45,10 @@ {% elif notice.is_group_join_request %}

    {{ notice.format_group_join_request|safe }}

    + + {% elif notice.is_add_user_to_group %} +

    {{ notice.format_add_user_to_group|safe }}

    + {% endif %}

    {{ notice.timestamp|translate_seahub_time }}