1# Copyright (c) 2012-2016 Seafile Ltd.
2# -*- coding: utf-8 -*-
3import datetime
4import os
5import json
6import logging
7
8from django.urls import reverse
9from django.db import models
10from django.conf import settings
11from django.forms import ModelForm, Textarea
12from django.utils.html import escape
13from django.utils.translation import ugettext as _
14from django.core.cache import cache
15from django.template.loader import render_to_string
16
17import seaserv
18from seaserv import seafile_api, ccnet_api
19
20from seahub.base.fields import LowerCaseCharField
21from seahub.base.templatetags.seahub_tags import email2nickname
22from seahub.invitations.models import Invitation
23from seahub.utils.repo import get_repo_shared_users
24from seahub.utils import normalize_cache_key
25from seahub.utils.timeutils import datetime_to_isoformat_timestr
26from seahub.constants import HASH_URLS
27from seahub.drafts.models import DraftReviewer
28from seahub.file_participants.utils import list_file_participants
29
30# Get an instance of a logger
31logger = logging.getLogger(__name__)
32
33
34class NotificationManager(models.Manager):
35    def create_sys_notification(self, message, is_primary=False):
36        """
37        Creates and saves a system notification.
38        """
39        notification = Notification()
40        notification.message = message
41        notification.primary = is_primary
42        notification.save()
43
44        return notification
45
46
47########## system notification
48class Notification(models.Model):
49    message = models.CharField(max_length=512)
50    primary = models.BooleanField(default=False, db_index=True)
51    objects = NotificationManager()
52
53    def update_notification_to_current(self):
54        self.primary = 1
55        self.save()
56
57class NotificationForm(ModelForm):
58    """
59    Form for adding notification.
60    """
61    class Meta:
62        model = Notification
63        fields = ('message', 'primary')
64        widgets = {
65            'message': Textarea(),
66        }
67
68########## user notification
69MSG_TYPE_GROUP_JOIN_REQUEST = 'group_join_request'
70MSG_TYPE_ADD_USER_TO_GROUP = 'add_user_to_group'
71MSG_TYPE_FILE_UPLOADED = 'file_uploaded'
72MSG_TYPE_REPO_SHARE = 'repo_share'
73MSG_TYPE_REPO_SHARE_TO_GROUP = 'repo_share_to_group'
74MSG_TYPE_USER_MESSAGE = 'user_message'
75MSG_TYPE_FILE_COMMENT = 'file_comment'
76MSG_TYPE_DRAFT_COMMENT = 'draft_comment'
77MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer'
78MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'
79MSG_TYPE_REPO_TRANSFER = 'repo_transfer'
80
81USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_'
82
83def file_uploaded_msg_to_json(file_name, repo_id, uploaded_to):
84    """Encode file uploaded message to json string.
85    """
86    return json.dumps({'file_name': file_name, 'repo_id': repo_id,
87                       'uploaded_to': uploaded_to})
88
89def repo_share_msg_to_json(share_from, repo_id, path, org_id):
90    return json.dumps({'share_from': share_from, 'repo_id': repo_id,
91                       'path': path, 'org_id': org_id})
92
93def repo_share_to_group_msg_to_json(share_from, repo_id, group_id, path, org_id):
94    return json.dumps({'share_from': share_from, 'repo_id': repo_id,
95                       'group_id': group_id, 'path': path, 'org_id': org_id})
96
97def group_msg_to_json(group_id, msg_from, message):
98    return json.dumps({'group_id': group_id, 'msg_from': msg_from,
99                       'message': message})
100
101def user_msg_to_json(message, msg_from):
102    return json.dumps({'message': message, 'msg_from': msg_from})
103
104def group_join_request_to_json(username, group_id, join_request_msg):
105    return json.dumps({'username': username, 'group_id': group_id,
106                       'join_request_msg': join_request_msg})
107
108def add_user_to_group_to_json(group_staff, group_id):
109    return json.dumps({'group_staff': group_staff,
110                       'group_id': group_id})
111
112def file_comment_msg_to_json(repo_id, file_path, author, comment):
113    return json.dumps({'repo_id': repo_id,
114                       'file_path': file_path,
115                       'author': author,
116                       'comment': comment})
117
118def draft_comment_msg_to_json(draft_id, author, comment):
119    return json.dumps({'draft_id': draft_id,
120                       'author': author,
121                       'comment': comment})
122
123def request_reviewer_msg_to_json(draft_id, from_user, to_user):
124    return json.dumps({'draft_id': draft_id,
125                       'from_user': from_user,
126                       'to_user': to_user})
127
128def guest_invitation_accepted_msg_to_json(invitation_id):
129    return json.dumps({'invitation_id': invitation_id})
130
131def repo_transfer_msg_to_json(org_id, repo_owner, repo_id, repo_name):
132    """Encode repo transfer message to json string.
133    """
134    return json.dumps({'org_id': org_id, 'repo_owner': repo_owner,
135        'repo_id': repo_id, 'repo_name': repo_name})
136
137def get_cache_key_of_unseen_notifications(username):
138    return normalize_cache_key(username,
139            USER_NOTIFICATION_COUNT_CACHE_PREFIX)
140
141
142class UserNotificationManager(models.Manager):
143    def _add_user_notification(self, to_user, msg_type, detail):
144        """Add generic user notification.
145
146        Arguments:
147        - `self`:
148        - `username`:
149        - `detail`:
150        """
151        n = super(UserNotificationManager, self).create(
152            to_user=to_user, msg_type=msg_type, detail=detail)
153        n.save()
154
155        cache_key = get_cache_key_of_unseen_notifications(to_user)
156        cache.delete(cache_key)
157
158        return n
159
160    def get_all_notifications(self, seen=None, time_since=None):
161        """Get all notifications of all users.
162
163        Arguments:
164        - `self`:
165        - `seen`:
166        - `time_since`:
167        """
168        qs = super(UserNotificationManager, self).all()
169        if seen is not None:
170            qs = qs.filter(seen=seen)
171        if time_since is not None:
172            qs = qs.filter(timestamp__gt=time_since)
173        return qs
174
175    def get_user_notifications(self, username, seen=None):
176        """Get all notifications(group_msg, grpmsg_reply, etc) of a user.
177
178        Arguments:
179        - `self`:
180        - `username`:
181        """
182        qs = super(UserNotificationManager, self).filter(to_user=username)
183        if seen is not None:
184            qs = qs.filter(seen=seen)
185        return qs
186
187    def remove_user_notifications(self, username):
188        """Remove all user notifications.
189
190        Arguments:
191        - `self`:
192        - `username`:
193        """
194        self.get_user_notifications(username).delete()
195
196    def count_unseen_user_notifications(self, username):
197        """
198
199        Arguments:
200        - `self`:
201        - `username`:
202        """
203        return super(UserNotificationManager, self).filter(
204            to_user=username, seen=False).count()
205
206    def seen_user_msg_notices(self, to_user, from_user):
207        """Mark priv message notices of a user as seen.
208        """
209        user_notices = super(UserNotificationManager, self).filter(
210            to_user=to_user, seen=False, msg_type=MSG_TYPE_USER_MESSAGE)
211        for notice in user_notices:
212            notice_from_user = notice.user_message_detail_to_dict().get('msg_from')
213            if from_user == notice_from_user:
214                if notice.seen is False:
215                    notice.seen = True
216                    notice.save()
217
218    def add_group_join_request_notice(self, to_user, detail):
219        """
220
221        Arguments:
222        - `self`:
223        - `to_user`:
224        - `detail`:
225        """
226        return self._add_user_notification(to_user,
227                                           MSG_TYPE_GROUP_JOIN_REQUEST, detail)
228
229    def set_add_user_to_group_notice(self, to_user, detail):
230        """
231
232        Arguments:
233        - `self`:
234        - `to_user`:
235        - `detail`:
236        """
237        return self._add_user_notification(to_user,
238                                           MSG_TYPE_ADD_USER_TO_GROUP,
239                                           detail)
240
241    def add_file_uploaded_msg(self, to_user, detail):
242        """
243
244        Arguments:
245        - `self`:
246        - `to_user`:
247        - `file_name`:
248        - `upload_to`:
249        """
250        return self._add_user_notification(to_user,
251                                           MSG_TYPE_FILE_UPLOADED, detail)
252
253    def add_repo_share_msg(self, to_user, detail):
254        """Notify ``to_user`` that others shared a repo to him/her.
255
256        Arguments:
257        - `self`:
258        - `to_user`:
259        - `repo_id`:
260        """
261        return self._add_user_notification(to_user,
262                                           MSG_TYPE_REPO_SHARE, detail)
263
264    def add_repo_share_to_group_msg(self, to_user, detail):
265        """Notify ``to_user`` that others shared a repo to group.
266
267        Arguments:
268        - `self`:
269        - `to_user`:
270        - `detail`:
271        """
272        return self._add_user_notification(to_user,
273                   MSG_TYPE_REPO_SHARE_TO_GROUP, detail)
274
275    def add_user_message(self, to_user, detail):
276        """Notify ``to_user`` that others sent a message to him/her.
277
278        Arguments:
279        - `self`:
280        - `to_user`:
281        - `detail`:
282        """
283        return self._add_user_notification(to_user,
284                                           MSG_TYPE_USER_MESSAGE, detail)
285
286    def add_file_comment_msg(self, to_user, detail):
287        """Notify ``to_user`` that others comment a file he can access.
288        """
289        return self._add_user_notification(to_user, MSG_TYPE_FILE_COMMENT, detail)
290
291    def add_draft_comment_msg(self, to_user, detail):
292        """Notify ``to_user`` that review creator
293        """
294        return self._add_user_notification(to_user, MSG_TYPE_DRAFT_COMMENT, detail)
295
296    def add_request_reviewer_msg(self, to_user, detail):
297        """Notify ``to_user`` that reviewer
298        """
299        return self._add_user_notification(to_user, MSG_TYPE_DRAFT_REVIEWER, detail)
300
301    def add_guest_invitation_accepted_msg(self, to_user, detail):
302        """Nofity ``to_user`` that a guest has accpeted an invitation.
303        """
304        return self._add_user_notification(
305            to_user, MSG_TYPE_GUEST_INVITATION_ACCEPTED, detail)
306
307    def add_repo_transfer_msg(self, to_user, detail):
308        """Nofity ``to_user`` that a library has been transfered to him/her.
309        """
310        return self._add_user_notification(
311            to_user, MSG_TYPE_REPO_TRANSFER, detail)
312
313
314class UserNotification(models.Model):
315    to_user = LowerCaseCharField(db_index=True, max_length=255)
316    msg_type = models.CharField(db_index=True, max_length=30)
317    detail = models.TextField()
318    timestamp = models.DateTimeField(db_index=True, default=datetime.datetime.now)
319    seen = models.BooleanField('seen', default=False)
320    objects = UserNotificationManager()
321
322    class InvalidDetailError(Exception):
323        pass
324
325    class Meta:
326        ordering = ["-timestamp"]
327
328    def __unicode__(self):
329        return '%s|%s|%s' % (self.to_user, self.msg_type, self.detail)
330
331    def is_seen(self):
332        """Returns value of ``self.seen`` but also changes it to ``True``.
333
334        Use this in a template to mark an unseen notice differently the first
335        time it is shown.
336
337        Arguments:
338        - `self`:
339        """
340        seen = self.seen
341        if seen is False:
342            self.seen = True
343            self.save()
344        return seen
345
346    def is_file_uploaded_msg(self):
347        """
348
349        Arguments:
350        - `self`:
351        """
352        return self.msg_type == MSG_TYPE_FILE_UPLOADED
353
354    def is_repo_share_msg(self):
355        """
356
357        Arguments:
358        - `self`:
359        """
360        return self.msg_type == MSG_TYPE_REPO_SHARE
361
362    def is_repo_share_to_group_msg(self):
363        """
364
365        Arguments:
366        - `self`:
367        """
368        return self.msg_type == MSG_TYPE_REPO_SHARE_TO_GROUP
369
370    def is_user_message(self):
371        """
372
373        Arguments:
374        - `self`:
375        """
376        return self.msg_type == MSG_TYPE_USER_MESSAGE
377
378    def is_group_join_request(self):
379        """
380
381        Arguments:
382        - `self`:
383        """
384        return self.msg_type == MSG_TYPE_GROUP_JOIN_REQUEST
385
386    def is_add_user_to_group(self):
387        """
388
389        Arguments:
390        - `self`:
391        """
392        return self.msg_type == MSG_TYPE_ADD_USER_TO_GROUP
393
394    def is_file_comment_msg(self):
395        return self.msg_type == MSG_TYPE_FILE_COMMENT
396
397    def is_draft_comment_msg(self):
398        return self.msg_type == MSG_TYPE_DRAFT_COMMENT
399
400    def is_draft_reviewer_msg(self):
401        return self.msg_type == MSG_TYPE_DRAFT_REVIEWER
402
403    def is_guest_invitation_accepted_msg(self):
404        return self.msg_type == MSG_TYPE_GUEST_INVITATION_ACCEPTED
405
406    def is_repo_transfer_msg(self):
407        return self.msg_type == MSG_TYPE_REPO_TRANSFER
408
409    def user_message_detail_to_dict(self):
410        """Parse user message detail, returns dict contains ``message`` and
411        ``msg_from``.
412
413        Arguments:
414        - `self`:
415
416        """
417        assert self.is_user_message()
418
419        try:
420            detail = json.loads(self.detail)
421        except ValueError:
422            msg_from = self.detail
423            message = None
424            return {'message': message, 'msg_from': msg_from}
425        else:
426            message = detail['message']
427            msg_from = detail['msg_from']
428            return {'message': message, 'msg_from': msg_from}
429
430    ########## functions used in templates
431    def format_msg(self):
432        if self.is_file_uploaded_msg():
433            return self.format_file_uploaded_msg()
434        elif self.is_repo_share_msg():
435            return self.format_repo_share_msg()
436        elif self.is_repo_share_to_group_msg():
437            return self.format_repo_share_to_group_msg()
438        elif self.is_group_join_request():
439            return self.format_group_join_request()
440        elif self.is_file_comment_msg():
441            return self.format_file_comment_msg()
442        elif self.is_draft_comment_msg():
443            return self.format_draft_comment_msg()
444        elif self.is_draft_reviewer_msg():
445            return self.format_draft_reviewer_msg()
446        elif self.is_guest_invitation_accepted_msg():
447            return self.format_guest_invitation_accepted_msg()
448        elif self.is_add_user_to_group():
449            return self.format_add_user_to_group()
450        elif self.is_repo_transfer_msg():
451            return self.format_repo_transfer_msg()
452        else:
453            return ''
454
455    def format_file_uploaded_msg(self):
456        """
457
458        Arguments:
459        - `self`:
460        """
461        try:
462            d = json.loads(self.detail)
463        except Exception as e:
464            logger.error(e)
465            return _("Internal Server Error")
466
467        filename = d['file_name']
468        repo_id = d['repo_id']
469        repo = seafile_api.get_repo(repo_id)
470        if repo:
471            if d['uploaded_to'] == '/':
472                # current upload path is '/'
473                file_path = '/' + filename
474                link = reverse('lib_view', args=[repo_id, repo.name, ''])
475                name = repo.name
476            else:
477                uploaded_to = d['uploaded_to'].rstrip('/')
478                file_path = uploaded_to + '/' + filename
479                link = reverse('lib_view', args=[repo_id, repo.name, uploaded_to.lstrip('/')])
480                name = os.path.basename(uploaded_to)
481            file_link = reverse('view_lib_file', args=[repo_id, file_path])
482
483            msg = _("A file named <a href='%(file_link)s'>%(file_name)s</a> is uploaded to <a href='%(link)s'>%(name)s</a>") % {
484                'file_link': file_link,
485                'file_name': escape(filename),
486                'link': link,
487                'name': escape(name),
488                }
489        else:
490            msg = _("A file named <strong>%(file_name)s</strong> is uploaded to <strong>Deleted Library</strong>") % {
491                'file_name': escape(filename),
492                }
493
494        return msg
495
496    def format_repo_share_msg(self):
497        """
498
499        Arguments:
500        - `self`:
501        """
502        try:
503            d = json.loads(self.detail)
504        except Exception as e:
505            logger.error(e)
506            return _("Internal Server Error")
507
508        share_from = email2nickname(d['share_from'])
509        repo_id = d['repo_id']
510        path = d.get('path', '/')
511        org_id = d.get('org_id', None)
512        repo = None
513        try:
514            if path == '/':
515                repo = seafile_api.get_repo(repo_id)
516            else:
517                if org_id:
518                    owner = seafile_api.get_org_repo_owner(repo_id)
519                    repo = seafile_api.get_org_virtual_repo(
520                        org_id, repo_id, path, owner)
521                else:
522                    owner = seafile_api.get_repo_owner(repo_id)
523                    repo = seafile_api.get_virtual_repo(repo_id, path, owner)
524
525        except Exception as e:
526            logger.error(e)
527            return None
528
529        if repo is None:
530            self.delete()
531            return None
532
533        if path == '/':
534            tmpl = 'notifications/notice_msg/repo_share_msg.html'
535        else:
536            tmpl = 'notifications/notice_msg/folder_share_msg.html'
537
538        lib_url = reverse('lib_view', args=[repo.id, repo.name, ''])
539        msg = render_to_string(tmpl, {
540            'user': share_from,
541            'lib_url': lib_url,
542            'lib_name': repo.name,
543        })
544
545        return msg
546
547    def format_repo_share_to_group_msg(self):
548        """
549
550        Arguments:
551        - `self`:
552        """
553        try:
554            d = json.loads(self.detail)
555        except Exception as e:
556            logger.error(e)
557            return _("Internal Server Error")
558
559        share_from = email2nickname(d['share_from'])
560        repo_id = d['repo_id']
561        group_id = d['group_id']
562        path = d.get('path', '/')
563        org_id = d.get('org_id', None)
564
565        repo = None
566        try:
567            group = ccnet_api.get_group(group_id)
568            if path == '/':
569                repo = seafile_api.get_repo(repo_id)
570            else:
571                if org_id:
572                    owner = seafile_api.get_org_repo_owner(repo_id)
573                    repo = seafile_api.get_org_virtual_repo(
574                        org_id, repo_id, path, owner)
575                else:
576                    owner = seafile_api.get_repo_owner(repo_id)
577                    repo = seafile_api.get_virtual_repo(repo_id, path, owner)
578        except Exception as e:
579            logger.error(e)
580            return None
581
582        if not repo or not group:
583            self.delete()
584            return None
585
586        if path == '/':
587            tmpl = 'notifications/notice_msg/repo_share_to_group_msg.html'
588        else:
589            tmpl = 'notifications/notice_msg/folder_share_to_group_msg.html'
590
591        lib_url = reverse('lib_view', args=[repo.id, repo.name, ''])
592        group_url = reverse('group', args=[group.id])
593        msg = render_to_string(tmpl, {
594            'user': share_from,
595            'lib_url': lib_url,
596            'lib_name': repo.name,
597            'group_url': group_url,
598            'group_name': group.group_name,
599        })
600
601        return msg
602
603    def format_group_join_request(self):
604        """
605
606        Arguments:
607        - `self`:
608        """
609        try:
610            d = json.loads(self.detail)
611        except Exception as e:
612            logger.error(e)
613            return _("Internal Server Error")
614
615        username = d['username']
616        group_id = d['group_id']
617        join_request_msg = d['join_request_msg']
618
619        group = ccnet_api.get_group(group_id)
620        if group is None:
621            self.delete()
622            return None
623
624        msg = _("User <a href='%(user_profile)s'>%(username)s</a> has asked to join group <a href='%(href)s'>%(group_name)s</a>, verification message: %(join_request_msg)s") % {
625            'user_profile': reverse('user_profile', args=[username]),
626            'username': username,
627            'href': HASH_URLS['GROUP_MEMBERS'] % {'group_id': group_id},
628            'group_name': escape(group.group_name),
629            'join_request_msg': escape(join_request_msg),
630            }
631        return msg
632
633    def format_add_user_to_group(self):
634        """
635
636        Arguments:
637        - `self`:
638        """
639        try:
640            d = json.loads(self.detail)
641        except Exception as e:
642            logger.error(e)
643            return _("Internal Server Error")
644
645        group_staff = d['group_staff']
646        group_id = d['group_id']
647
648        group = ccnet_api.get_group(group_id)
649        if group is None:
650            self.delete()
651            return None
652
653        msg = _("User <a href='%(user_profile)s'>%(group_staff)s</a> has added you to group <a href='%(href)s'>%(group_name)s</a>") % {
654            'user_profile': reverse('user_profile', args=[group_staff]),
655            'group_staff': escape(email2nickname(group_staff)),
656            'href': reverse('group', args=[group_id]),
657            'group_name': escape(group.group_name)}
658        return msg
659
660    def format_file_comment_msg(self):
661        try:
662            d = json.loads(self.detail)
663        except Exception as e:
664            logger.error(e)
665            return _("Internal Server Error")
666
667        repo_id = d['repo_id']
668        file_path = d['file_path']
669        author = d['author']
670        comment = d['comment']
671
672        repo = seafile_api.get_repo(repo_id)
673        if repo is None or not seafile_api.get_file_id_by_path(repo.id,
674                                                               file_path):
675            self.delete()
676            return None
677
678        file_name = os.path.basename(file_path)
679        msg = _("File <a href='%(file_url)s'>%(file_name)s</a> has a new comment from user %(author)s") % {
680            'file_url': reverse('view_lib_file', args=[repo_id, file_path]),
681            'file_name': escape(file_name),
682            'author': escape(email2nickname(author)),
683        }
684        return msg
685
686    def format_draft_comment_msg(self):
687        try:
688            d = json.loads(self.detail)
689        except Exception as e:
690            logger.error(e)
691            return _("Internal Server Error")
692
693        draft_id = d['draft_id']
694        author = d['author']
695
696        msg = _("<a href='%(file_url)s'>Draft #%(draft_id)s</a> has a new comment from user %(author)s") % {
697            'draft_id': draft_id,
698            'file_url': reverse('drafts:draft', args=[draft_id]),
699            'author': escape(email2nickname(author)),
700        }
701        return msg
702
703    def format_draft_reviewer_msg(self):
704        try:
705            d = json.loads(self.detail)
706        except Exception as e:
707            logger.error(e)
708            return _("Internal Server Error")
709
710        draft_id = d['draft_id']
711        from_user = d['from_user']
712
713        msg = _("%(from_user)s has sent you a request for <a href='%(file_url)s'>draft #%(draft_id)s</a>") % {
714            'draft_id': draft_id,
715            'file_url': reverse('drafts:draft', args=[draft_id]),
716            'from_user': escape(email2nickname(from_user))
717        }
718        return msg
719
720    def format_guest_invitation_accepted_msg(self):
721        try:
722            d = json.loads(self.detail)
723        except Exception as e:
724            logger.error(e)
725            return _("Internal Server Error")
726
727        inv_id = d['invitation_id']
728        try:
729            inv = Invitation.objects.get(pk=inv_id)
730        except Invitation.DoesNotExist:
731            self.delete()
732            return
733
734        # Use same msg as in notice_email.html, so there will be only one msg
735        # in django.po.
736        msg = _('Guest %(user)s accepted your <a href="%(url_base)s%(inv_url)s">invitation</a> at %(time)s.') % {
737            'user': inv.accepter,
738            'url_base': '',
739            'inv_url': settings.SITE_ROOT + '#invitations/',
740            'time': inv.accept_time.strftime("%Y-%m-%d %H:%M:%S"),
741        }
742        return msg
743
744    def format_repo_transfer_msg(self):
745        """
746
747        Arguments:
748        - `self`:
749        """
750        try:
751            d = json.loads(self.detail)
752        except Exception as e:
753            logger.error(e)
754            return _("Internal Server Error")
755
756        repo_owner_name = email2nickname(d['repo_owner'])
757        repo_id = d['repo_id']
758        repo_name = d['repo_name']
759        repo_url = reverse('lib_view', args=[repo_id, repo_name, ''])
760        msg = _('%(user)s has transfered a library named <a href="%(repo_url)s">%(repo_name)s</a> to you.') % {
761            'user': repo_owner_name,
762            'repo_url': repo_url,
763            'repo_name': repo_name,
764        }
765        return msg
766
767
768########## handle signals
769from django.dispatch import receiver
770
771from seahub.signals import upload_file_successful, comment_file_successful, repo_transfer
772from seahub.group.signals import group_join_request, add_user_to_group
773from seahub.share.signals import share_repo_to_user_successful, \
774    share_repo_to_group_successful
775from seahub.invitations.signals import accept_guest_invitation_successful
776from seahub.drafts.signals import comment_draft_successful, \
777        request_reviewer_successful
778
779@receiver(upload_file_successful)
780def add_upload_file_msg_cb(sender, **kwargs):
781    """Notify repo owner when others upload files to his/her folder from shared link.
782    """
783    repo_id = kwargs.get('repo_id', None)
784    file_path = kwargs.get('file_path', None)
785    owner = kwargs.get('owner', None)
786
787    assert repo_id and file_path and owner is not None, 'Arguments error'
788
789    filename = os.path.basename(file_path)
790    folder_path = os.path.dirname(file_path)
791
792    detail = file_uploaded_msg_to_json(filename, repo_id, folder_path)
793    UserNotification.objects.add_file_uploaded_msg(owner, detail)
794
795@receiver(share_repo_to_user_successful)
796def add_share_repo_msg_cb(sender, **kwargs):
797    """Notify user when others share repos to him/her.
798    """
799    from_user = kwargs.get('from_user', None)
800    to_user = kwargs.get('to_user', None)
801    repo = kwargs.get('repo', None)
802    path = kwargs.get('path', None)
803    org_id = kwargs.get('org_id', None)
804
805    assert from_user and to_user and repo and path is not None, 'Arguments error'
806
807    detail = repo_share_msg_to_json(from_user, repo.id, path, org_id)
808    UserNotification.objects.add_repo_share_msg(to_user, detail)
809
810@receiver(share_repo_to_group_successful)
811def add_share_repo_to_group_msg_cb(sender, **kwargs):
812    """Notify group member when others share repos to group.
813    """
814    from_user = kwargs.get('from_user', None)
815    group_id = kwargs.get('group_id', None)
816    repo = kwargs.get('repo', None)
817    path = kwargs.get('path', None)
818    org_id = kwargs.get('org_id', None)
819
820    assert from_user and group_id and repo and path is not None, 'Arguments error'
821
822    members = ccnet_api.get_group_members(int(group_id))
823    for member in members:
824        to_user = member.user_name
825        if to_user == from_user:
826            continue
827        detail = repo_share_to_group_msg_to_json(from_user, repo.id, group_id, path, org_id)
828        UserNotification.objects.add_repo_share_to_group_msg(to_user, detail)
829
830@receiver(group_join_request)
831def group_join_request_cb(sender, **kwargs):
832    staffs = kwargs['staffs']
833    username = kwargs['username']
834    group_id = kwargs['group'].id
835    join_request_msg = kwargs['join_request_msg']
836
837    detail = group_join_request_to_json(username, group_id,
838                                        join_request_msg)
839    for staff in staffs:
840        UserNotification.objects.add_group_join_request_notice(to_user=staff,
841                                                               detail=detail)
842
843@receiver(add_user_to_group)
844def add_user_to_group_cb(sender, **kwargs):
845    group_staff = kwargs['group_staff']
846    group_id = kwargs['group_id']
847    added_user = kwargs['added_user']
848
849    detail = add_user_to_group_to_json(group_staff,
850                                       group_id)
851
852    UserNotification.objects.set_add_user_to_group_notice(to_user=added_user,
853                                                          detail=detail)
854
855@receiver(comment_file_successful)
856def comment_file_successful_cb(sender, **kwargs):
857    """ send notification to file participants
858    """
859    repo = kwargs['repo']
860    repo_owner = kwargs['repo_owner']
861    file_path = kwargs['file_path']
862    comment = kwargs['comment']
863    author = kwargs['author']
864
865    notify_users = list_file_participants(repo.id, file_path)
866    notify_users = [x for x in notify_users if x != author]
867    for u in notify_users:
868        detail = file_comment_msg_to_json(repo.id, file_path, author, comment)
869        UserNotification.objects.add_file_comment_msg(u, detail)
870
871@receiver(comment_draft_successful)
872def comment_draft_successful_cb(sender, **kwargs):
873    draft = kwargs['draft']
874    comment = kwargs['comment']
875    author = kwargs['author']
876
877    detail = draft_comment_msg_to_json(draft.id, author, comment)
878
879    if draft.username != author:
880        UserNotification.objects.add_draft_comment_msg(draft.username, detail)
881
882    reviewers = DraftReviewer.objects.filter(draft=draft)
883    for r in reviewers:
884        if r.reviewer != author:
885            UserNotification.objects.add_draft_comment_msg(r.reviewer, detail)
886
887@receiver(request_reviewer_successful)
888def requeset_reviewer_successful_cb(sender, **kwargs):
889    from_user = kwargs['from_user']
890    draft_id = kwargs['draft_id']
891    to_user = kwargs['to_user']
892
893    detail = request_reviewer_msg_to_json(draft_id, from_user, to_user)
894
895    UserNotification.objects.add_request_reviewer_msg(to_user, detail)
896
897@receiver(accept_guest_invitation_successful)
898def accept_guest_invitation_successful_cb(sender, **kwargs):
899    inv_obj = kwargs['invitation_obj']
900
901    detail = guest_invitation_accepted_msg_to_json(inv_obj.pk)
902    UserNotification.objects.add_guest_invitation_accepted_msg(
903        inv_obj.inviter, detail)
904
905@receiver(repo_transfer)
906def repo_transfer_cb(sender, **kwargs):
907
908    org_id = kwargs['org_id']
909    repo_owner = kwargs['repo_owner']
910    to_user = kwargs['to_user']
911    repo_id = kwargs['repo_id']
912    repo_name = kwargs['repo_name']
913
914    detail = repo_transfer_msg_to_json(org_id, repo_owner, repo_id, repo_name)
915    UserNotification.objects.add_repo_transfer_msg(to_user, detail)
916