def test_last_read_post(self): # scenario - topic1 : # post1 - user1 - unread # post2 - user2 - unread self.assertEqual( self.post1, self.topic1.last_read_post(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read mark_read(self.topic1, user=self.profile1.user) self.assertEqual( self.post2, self.topic1.last_read_post(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read # post3 - user2 - unread PrivatePostFactory( privatetopic=self.topic1, author=self.profile2.user, position_in_topic=3) self.assertEqual( self.post2, self.topic1.last_read_post(self.profile1.user))
def post(self, request, *args, **kwargs): validation = get_object_or_404(Validation, pk=kwargs['pk']) if validation.validator: validation.validator = None validation.date_reserve = None validation.status = 'PENDING' validation.save() messages.info(request, _("Ce contenu n'est plus réservé.")) return redirect(reverse('validation:list')) else: validation.validator = request.user validation.date_reserve = datetime.now() validation.status = 'PENDING_V' validation.save() versioned = validation.content.load_version(sha=validation.version) msg = render_to_string( 'tutorialv2/messages/validation_reserve.md', { 'content': versioned, 'url': versioned.get_absolute_url() + '?version=' + validation.version, }) authors = list(validation.content.authors.all()) if validation.validator in authors: authors.remove(validation.validator) if len(authors) > 0: if not validation.content.validation_private_message: validation.content.validation_private_message = send_mp( validation.validator, authors, _('Contenu réservé - {0}').format( validation.content.title), validation.content.title, msg, True, leave=False, direct=False, mark_as_read=True, hat=get_hat_from_settings('validation'), ) validation.content.save(force_slug_update=False) else: send_message_mp( validation.validator, validation.content.validation_private_message, msg) mark_read(validation.content.validation_private_message, validation.validator) messages.info( request, _('Ce contenu a bien été réservé par {0}.').format( request.user.username)) return redirect( reverse('content:view', args=[validation.content.pk, validation.content.slug]) + '?version=' + validation.version)
def test_reuse_old_notification(self): """ When there already is a read notification for a given content, we reuse it. """ topic = send_mp(author=self.user1, users=[self.user2], title="Testing", subtitle="", text="", leave=False) send_message_mp(self.user2, topic, "", send_by_mail=True) notifications = Notification.objects.get_unread_notifications_of( self.user1) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object) mark_read(topic, self.user1) send_message_mp(self.user2, topic, "", send_by_mail=True) notifications = Notification.objects.filter( subscription__user=self.user1) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first())
def test_generate_a_notification_after_new_post(self): """ When a user posts on a private topic, we generate a notification for all participants. """ topic = send_mp(author=self.user1, users=[self.user2, self.user3], title="Testing", subtitle="", text="", leave=False) notifications = Notification.objects.get_unread_notifications_of( self.user2) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object) mark_read(topic, self.user2) notifications = Notification.objects.get_unread_notifications_of( self.user2) self.assertEqual(0, len(notifications)) send_message_mp(self.user3, topic, "") notifications = Notification.objects.get_unread_notifications_of( self.user2) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object)
def test_no_interference(self): mark_read(self.topic1, self.author.user) topic_read_old = PrivateTopicRead.objects.filter(privatetopic=self.topic1, user=self.author.user) self.assertTrue(self.client.login(username=self.participant.user.username, password="******")) self.client.get(reverse("private-post-unread") + "?message=" + str(self.post2.pk), follow=True) topic_read_new = PrivateTopicRead.objects.filter(privatetopic=self.topic1, user=self.author.user) self.assertQuerysetEqual(topic_read_old, [repr(t) for t in topic_read_new])
def test_unicode(self): """ test the unicode return """ ref = u'<Sujet « {0} » lu par {1}, #{2}>'.format( self.topic1, self.profile2.user, self.post2.pk) mark_read(self.topic1, self.profile2.user) private_topic = PrivateTopicRead.objects.filter( privatetopic=self.topic1, user=self.profile2.user).first() self.assertEqual(private_topic.__unicode__(), ref)
def send_mp( author, users, title, subtitle, text, send_by_mail=True, leave=True, direct=False, mark_as_read=False, hat=None, automatically_read=None, ): """ Send a private message in a new private topic. :param author: sender of the message and author of the private topic :param users: list of users receiving the message (participants of the private topic) :param title: title of the private topic :param subtitle: subtitle of the private topic :param text: content of the private message :param send_by_mail: :param direct: send a mail directly without mp (ex : ban members who wont connect again) :param leave: if True, do not add the sender to the topic :param mark_as_read: :param hat: hat with which to send the private message :param automatically_read: a user or a list of users that will automatically be marked as having read of the mp :raise UnreachableUserError: """ # Creating the thread limit = PrivateTopic._meta.get_field("title").max_length n_topic = PrivateTopic() n_topic.title = title[:limit] n_topic.subtitle = subtitle n_topic.pubdate = datetime.now() n_topic.author = author n_topic.save() # Add all participants on the MP. for participants in users: n_topic.participants.add(participants) topic = send_message_mp(author, n_topic, text, send_by_mail, direct, hat) if mark_as_read: mark_read(topic, author) if automatically_read: if not isinstance(automatically_read, list): automatically_read = [automatically_read] for not_notified_user in automatically_read: mark_read(n_topic, not_notified_user) if leave: topic.remove_participant(topic.author) topic.save() return topic
def test_unicode(self): """ test the unicode return """ ref = u'<Sujet "{0}" lu par {1}, #{2}>'.format(self.topic1, self.profile2.user, self.post2.pk) mark_read(self.topic1, self.profile2.user) pt = PrivateTopicRead.objects.filter(privatetopic=self.topic1, user=self.profile2.user) self.assertEqual(str(pt[0]), ref)
def test_first_unread_post(self): # scenario - topic1 : # post1 - user1 - unread # post2 - user2 - unread self.assertEqual(self.post1, self.topic1.first_unread_post(self.user1)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read # post3 - user2 - unread mark_read(self.topic1, self.user1) post3 = PrivatePostFactory(privatetopic=self.topic1, author=self.user2, position_in_topic=3) self.assertEqual(post3, self.topic1.first_unread_post(self.user1))
def send_mp(author, users, title, subtitle, text, send_by_mail=True, leave=True, direct=False, mark_as_read=False, hat=None, automaticaly_read=None): """ Send MP at members. Most of the param are obvious, excepted : :param direct: send a mail directly without mp (ex : ban members who wont connect again) :param leave: the author leave the conversation (usefull for the bot : it wont read the response a member could send) :param automaticaly_read: a user or a list of users that will automatically be marked as reader of the mp """ # Creating the thread limit = PrivateTopic._meta.get_field('title').max_length n_topic = PrivateTopic() n_topic.title = title[:limit] n_topic.subtitle = subtitle n_topic.pubdate = datetime.now() n_topic.author = author n_topic.save() # Add all participants on the MP. for participants in users: n_topic.participants.add(participants) topic = send_message_mp(author, n_topic, text, send_by_mail, direct, hat) if mark_as_read: mark_read(topic, author) if automaticaly_read: if not isinstance(automaticaly_read, list): automaticaly_read = [automaticaly_read] for not_notified_user in automaticaly_read: mark_read(n_topic, not_notified_user) if leave: move = topic.participants.first() topic.author = move topic.participants.remove(move) topic.save() return topic
def send_mp( author, users, title, subtitle, text, send_by_mail=True, leave=True, direct=False, hat=None, automatically_read=None, ): """ Send a private message in a new private topic. :param author: sender of the message and author of the private topic :param users: list of users receiving the message (participants of the private topic) :param title: title of the private topic :param subtitle: subtitle of the private topic :param text: content of the private message :param send_by_mail: :param direct: send a mail directly without mp (ex : ban members who wont connect again) :param leave: if True, do not add the sender to the topic :param hat: hat with which to send the private message :param automatically_read: a user or a list of users that will automatically be marked as having read of the mp :raise UnreachableUserError: """ n_topic = PrivateTopic.create(title=title, subtitle=subtitle, author=author, recipients=users) signals.topic_created.send(sender=PrivateTopic, topic=n_topic, by_email=send_by_mail) topic = send_message_mp(author, n_topic, text, send_by_mail, direct, hat) if automatically_read: if not isinstance(automatically_read, list): automatically_read = [automatically_read] for not_notified_user in automatically_read: mark_read(n_topic, not_notified_user) if leave: topic.remove_participant(topic.author) topic.save() return topic
def test_mark_read(self, topic_read): self.assertTrue(self.topic1.is_unread(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read mark_read(self.topic1, self.profile1.user) self.assertFalse(self.topic1.is_unread(self.profile1.user)) self.assertEqual(topic_read.send.call_count, 1) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read # post3 - user2 - unread PrivatePostFactory(privatetopic=self.topic1, author=self.profile2.user, position_in_topic=3) self.assertTrue(self.topic1.is_unread(self.profile1.user))
def test_mark_read(self): self.assertTrue(self.topic1.never_read(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read mark_read(self.topic1, self.profile1.user) self.assertFalse(self.topic1.never_read(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read # post3 - user2 - unread PrivatePostFactory(privatetopic=self.topic1, author=self.profile2.user, position_in_topic=3) self.assertTrue(self.topic1.never_read(self.profile1.user))
def test_mark_read_a_notification(self): """ When we mark a private topic as read, we mark its notification as read. """ topic = send_mp(author=self.user1, users=[self.user2, self.user3], title='Testing', subtitle='', text='', leave=False) notifications = Notification.objects.get_unread_notifications_of(self.user2) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object) mark_read(topic, self.user2) notifications = Notification.objects.get_unread_notifications_of(self.user2) self.assertEqual(0, len(notifications))
def test_mark_read(self): self.assertTrue(self.topic1.is_unread(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read mark_read(self.topic1, self.profile1.user) self.assertFalse(self.topic1.is_unread(self.profile1.user)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read # post3 - user2 - unread PrivatePostFactory( privatetopic=self.topic1, author=self.profile2.user, position_in_topic=3) self.assertTrue(self.topic1.is_unread(self.profile1.user))
def test_is_unread(self): # scenario - topic1 : # post1 - user1 - unread # post2 - user2 - unread self.assertTrue(self.topic1.is_unread(self.user1)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read mark_read(self.topic1, self.user1) self.assertFalse(self.topic1.is_unread(self.user1)) # scenario - topic1 : # post1 - user1 - read # post2 - user2 - read # post3 - user2 - unread PrivatePostFactory(privatetopic=self.topic1, author=self.user2, position_in_topic=3) self.assertTrue(self.topic1.is_unread(self.user1))
def test_reuse_old_notification(self): """ When there already is a read notification for a given content, we reuse it. """ topic = send_mp(author=self.user1, users=[self.user2], title='Testing', subtitle='', text='', leave=False) send_message_mp(self.user2, topic, '', send_by_mail=True) notifications = Notification.objects.get_unread_notifications_of(self.user1) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object) mark_read(topic, self.user1) send_message_mp(self.user2, topic, '', send_by_mail=True) notifications = Notification.objects.filter(subscription__user=self.user1) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first())
def send_mp( author, users, title, subtitle, text, send_by_mail=True, leave=True, direct=False, mark_as_read=False, hat=None): """ Send MP at members. Most of the param are obvious, excepted : * direct : send a mail directly without mp (ex : ban members who wont connect again) * leave : the author leave the conversation (usefull for the bot : it wont read the response a member could send) """ # Creating the thread limit = PrivateTopic._meta.get_field('title').max_length n_topic = PrivateTopic() n_topic.title = title[:limit] n_topic.subtitle = subtitle n_topic.pubdate = datetime.now() n_topic.author = author n_topic.save() # Add all participants on the MP. for participants in users: n_topic.participants.add(participants) topic = send_message_mp(author, n_topic, text, send_by_mail, direct, hat) if mark_as_read: mark_read(topic, author) if leave: move = topic.participants.first() topic.author = move topic.participants.remove(move) topic.save() return topic
def send_mp( author, users, title, subtitle, text, send_by_mail=True, leave=True, direct=False, mark_as_read=False): """ Send MP at members. Most of the param are obvious, excepted : * direct : send a mail directly without mp (ex : ban members who wont connect again) * leave : the author leave the conversation (usefull for the bot : it wont read the response a member could send) """ # Creating the thread limit = PrivateTopic._meta.get_field('title').max_length n_topic = PrivateTopic() n_topic.title = title[:limit] n_topic.subtitle = subtitle n_topic.pubdate = datetime.now() n_topic.author = author n_topic.save() # Add all participants on the MP. for part in users: n_topic.participants.add(part) topic = send_message_mp(author, n_topic, text, send_by_mail, direct) if mark_as_read: mark_read(topic, author) if leave: move = topic.participants.first() topic.author = move topic.participants.remove(move) topic.save() return topic
def get(self, request, *args, **kwargs): """ Lists all private posts of a given private topic for an authenticated member. --- parameters: - name: Authorization description: Bearer token to make an authenticated request. required: true paramType: header - name: X-Data-Format description: Specify "Html" or "Markdown" for the desired resource. Defaults to "Markdown". required: false paramType: header - name: page description: Restricts output to the given page number. required: false paramType: query - name: page_size description: Sets the number of private posts per page. required: false paramType: query - name: ordering description: Sorts the results. You can order by (-)position_in_topic, (-)pubdate or (-)update. paramType: query - name: expand description: Returns an object instead of an identifier representing the given field. required: false paramType: query responseMessages: - code: 401 message: Not Authenticated - code: 404 message: Not Found """ response = self.list(request, *args, **kwargs) topic = get_object_or_404(PrivateTopic, pk=self.kwargs.get("pk_ptopic")) mark_read(topic, self.request.user) return response
def get(self, request, *args, **kwargs): """ Lists all private posts of a given private topic for an authenticated member. --- parameters: - name: Authorization description: Bearer token to make an authenticated request. required: true paramType: header - name: X-Data-Format description: Specify "Html" or "Markdown" for the desired resource. Defaults to "Markdown". required: false paramType: header - name: page description: Restricts output to the given page number. required: false paramType: query - name: page_size description: Sets the number of private posts per page. required: false paramType: query - name: ordering description: Sorts the results. You can order by (-)position_in_topic, (-)pubdate or (-)update. paramType: query - name: expand description: Returns an object instead of an identifier representing the given field. required: false paramType: query responseMessages: - code: 401 message: Not Authenticated - code: 404 message: Not Found """ response = self.list(request, *args, **kwargs) topic = get_object_or_404(PrivateTopic, pk=self.kwargs.get('pk_ptopic')) mark_read(topic, self.request.user) return response
def test_generate_a_notification_after_new_post(self): """ When a user posts on a private topic, we generate a notification for all participants. """ topic = send_mp(author=self.user1, users=[self.user2, self.user3], title='Testing', subtitle='', text='', leave=False) notifications = Notification.objects.get_unread_notifications_of(self.user2) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object) mark_read(topic, self.user2) notifications = Notification.objects.get_unread_notifications_of(self.user2) self.assertEqual(0, len(notifications)) send_message_mp(self.user3, topic, '') notifications = Notification.objects.get_unread_notifications_of(self.user2) self.assertEqual(1, len(notifications)) self.assertIsNotNone(notifications.first()) self.assertEqual(topic.last_message, notifications.first().content_object)
def perform_list(self, instance, user=None): if never_privateread(instance, user): mark_read(instance, user)
def test_never_privateread(self): self.assertTrue(never_privateread(self.topic1, self.profile1.user)) mark_read(self.topic1, self.profile1.user) self.assertFalse(never_privateread(self.topic1, self.profile1.user))
def test_never_privateread(self): self.assertTrue(is_privatetopic_unread(self.topic1, self.profile1.user)) mark_read(self.topic1, self.profile1.user) self.assertFalse(is_privatetopic_unread(self.topic1, self.profile1.user))
def send_message_mp(author, n_topic, text, send_by_mail=True, direct=False, hat=None, no_notification_for=None): """ Send a post in an MP. Most of the param are obvious, excepted : * direct : send a mail directly without mp (ex : ban members who wont connect again) * leave : the author leave the conversation (usefull for the bot : it wont read the response a member could send) """ # Getting the position of the post if n_topic.last_message is None: pos = 1 else: pos = n_topic.last_message.position_in_topic + 1 # Add the first message post = PrivatePost() post.privatetopic = n_topic post.author = author post.text = text post.text_html = emarkdown(text) post.pubdate = datetime.now() post.position_in_topic = pos post.hat = hat post.save() n_topic.last_message = post n_topic.save() if not direct: signals.new_content.send(sender=post.__class__, instance=post, by_email=send_by_mail, no_notification_for=no_notification_for) if send_by_mail and direct: subject = '{} : {}'.format(settings.ZDS_APP['site']['literal_name'], n_topic.title) from_email = '{} <{}>'.format( settings.ZDS_APP['site']['literal_name'], settings.ZDS_APP['site']['email_noreply']) for recipient in n_topic.participants.values_list('email', flat=True): message_html = render_to_string('email/direct.html', {'msg': emarkdown(text)}) message_txt = render_to_string('email/direct.txt', {'msg': text}) msg = EmailMultiAlternatives(subject, message_txt, from_email, [recipient]) msg.attach_alternative(message_html, 'text/html') try: msg.send() except Exception as e: logger.exception('Message was not sent to %s due to %s', recipient, e) if no_notification_for: if not isinstance(no_notification_for, list): no_notification_for = [no_notification_for] for not_notified_user in no_notification_for: mark_read(n_topic, not_notified_user) if author.pk not in [p.pk for p in n_topic.participants.all() ] and author.pk != n_topic.author.pk: n_topic.participants.add(author) n_topic.save() return n_topic
def send_message_mp(author, n_topic, text, send_by_mail=True, direct=False, hat=None, no_notification_for=None): """ Send a post in an MP. :param author: sender of the private message :param n_topic: topic in which it will be sent :param text: content of the message :param send_by_mail: if True, also notify by email :param direct: send a mail directly without private message (ex : banned members who won't connect again) :param hat: hat attached to the message :param no_notification_for: list of participants who won't be notified of the message """ # Getting the position of the post if n_topic.last_message is None: pos = 1 else: pos = n_topic.last_message.position_in_topic + 1 # Add the first message post = PrivatePost() post.privatetopic = n_topic post.author = author post.text = text post.text_html = emarkdown(text) post.pubdate = datetime.now() post.position_in_topic = pos post.hat = hat post.save() n_topic.last_message = post n_topic.save() if not direct: signals.message_added.send(sender=post.__class__, post=post, by_email=send_by_mail, no_notification_for=no_notification_for) if send_by_mail and direct: subject = "{} : {}".format(settings.ZDS_APP["site"]["literal_name"], n_topic.title) from_email = "{} <{}>".format( settings.ZDS_APP["site"]["literal_name"], settings.ZDS_APP["site"]["email_noreply"]) for recipient in n_topic.participants.values_list("email", flat=True): message_html = render_to_string("email/direct.html", {"msg": emarkdown(text)}) message_txt = render_to_string("email/direct.txt", {"msg": text}) msg = EmailMultiAlternatives(subject, message_txt, from_email, [recipient]) msg.attach_alternative(message_html, "text/html") try: msg.send() except Exception as e: logger.exception("Message was not sent to %s due to %s", recipient, e) if no_notification_for: if not isinstance(no_notification_for, list): no_notification_for = [no_notification_for] for not_notified_user in no_notification_for: mark_read(n_topic, not_notified_user) # There's no need to inform of the new participant # because participants are already notified through the `message_added` signal. # If we tried to add the bot, that's fine (a better solution would be welcome though) with suppress(NotReachableError): n_topic.add_participant(author, silent=True) n_topic.save() return n_topic
def post(self, request, *args, **kwargs): validation = get_object_or_404(Validation, pk=kwargs["pk"]) if validation.validator: validation.validator = None validation.date_reserve = None validation.status = "PENDING" validation.save() messages.info(request, _("Ce contenu n'est plus réservé.")) return redirect(reverse("validation:list")) else: validation.validator = request.user validation.date_reserve = datetime.now() validation.status = "PENDING_V" validation.save() versioned = validation.content.load_version(sha=validation.version) msg = render_to_string( "tutorialv2/messages/validation_reserve.md", { "content": versioned, "url": versioned.get_absolute_url() + "?version=" + validation.version, }, ) authors = list(validation.content.authors.all()) if validation.validator in authors: authors.remove(validation.validator) if len(authors) > 0: if not validation.content.validation_private_message: validation.content.validation_private_message = send_mp( validation.validator, authors, _("Contenu réservé - {0}").format( validation.content.title), validation.content.title, msg, send_by_mail=True, leave=False, direct=False, mark_as_read=True, hat=get_hat_from_settings("validation"), ) validation.content.save() else: send_message_mp( validation.validator, validation.content.validation_private_message, msg) mark_read(validation.content.validation_private_message, validation.validator) messages.info( request, _("Ce contenu a bien été réservé par {0}.").format( request.user.username)) return redirect( reverse("content:view", args=[validation.content.pk, validation.content.slug]) + "?version=" + validation.version)