diff --git a/.coveragerc b/.coveragerc index 6af6813..f69552e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,5 @@ [run] omit = */settings/* - */wsgi.py \ No newline at end of file + */wsgi.py + authors/apps/notifications/cron_job.py \ No newline at end of file diff --git a/authors/apps/articles/views.py b/authors/apps/articles/views.py index fa655c1..adf310a 100644 --- a/authors/apps/articles/views.py +++ b/authors/apps/articles/views.py @@ -9,9 +9,7 @@ from rest_framework.permissions import (AllowAny, IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly) from rest_framework.response import Response - from taggit.models import Tag - from .exceptions import CatHasNoArticles, TagHasNoArticles from .models import (Article, Bookmark, Category, LikeArticle, RateArticle, Reported) @@ -26,6 +24,12 @@ TagSerializer) from .utils import shareArticleMail +from .exceptions import TagHasNoArticles, CatHasNoArticles +from .models import Article, LikeArticle, RateArticle, Category +from rest_framework import filters +from django.db.models.signals import post_save +from django.dispatch import receiver +from authors.apps.notifications.models import notify_follower class TagListAPIView(generics.ListAPIView): """ List all tags """ @@ -54,7 +58,7 @@ class CategoryListCreateAPIView(generics.ListCreateAPIView): """ List / Create categories """ queryset = Category.objects.all() - permission_classes = (AllowAny,) + permission_classes = (IsAuthenticated,) serializer_class = CategorySerializer renderer_classes = (CategoryJSONRenderer,) @@ -69,7 +73,7 @@ def create(self, request): class CategoryRetrieveAPIView(generics.RetrieveAPIView): """ Get articles under a specific category """ - permission_classes = (AllowAny,) + permission_classes = (IsAuthenticated,) serializer_class = ArticleSerializer def retrieve(self, request, *args, **kwargs): @@ -117,6 +121,16 @@ def get_queryset(self): queryset = queryset.filter(title__icontains=title) return queryset +@receiver(post_save, sender=Article) +def notify_follower_reciever(sender, instance, created, **kwargs): + """ + Send a notification after the article being created is saved. + """ + if created: + message = ("Author " + instance.author.username + + " has published an article. Title: " + instance.title) + notify_follower(instance.author, message, instance) + class ArticleAPIDetailsView(generics.RetrieveUpdateDestroyAPIView): """retreive, update and delete an article """ diff --git a/authors/apps/comments/views.py b/authors/apps/comments/views.py index 2880ef1..e96a151 100644 --- a/authors/apps/comments/views.py +++ b/authors/apps/comments/views.py @@ -14,6 +14,11 @@ CommentThreadJSONRenderer) from .serializers import (CommentChildSerializer, CommentHistorySerializer, CommentSerializer, LikeCommentSerializer) + +from django.db.models.signals import post_save +from django.dispatch import receiver +from authors.apps.profiles.models import Profile +from authors.apps.notifications.models import notify_comment_follower class CommentListCreateView(generics.ListCreateAPIView): @@ -62,6 +67,20 @@ def get(self, request, *args, **kwargs): serializer = self.serializer_class(comment, many=True) return Response(serializer.data) +@receiver(post_save, sender=Comment) +def notify_follower_reciever(sender, instance, created, **kwargs): + """ + Send a notification after the article being created is saved. + """ + if created: + message = (instance.author.username + + " has commented on an article that you favorited.") + #import pdb;pdb.set_trace() + + article_id=instance.slug.id + + notify_comment_follower(article_id, message, instance) + class CommentsView(generics.RetrieveUpdateDestroyAPIView): serializer_class = CommentSerializer diff --git a/authors/apps/notifications/__init__.py b/authors/apps/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authors/apps/notifications/admin.py b/authors/apps/notifications/admin.py new file mode 100644 index 0000000..cf7be28 --- /dev/null +++ b/authors/apps/notifications/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import Notification, CommentNotification + +# Register your models here. + +admin.site.register(Notification) +admin.site.register(CommentNotification) + + diff --git a/authors/apps/notifications/apps.py b/authors/apps/notifications/apps.py new file mode 100644 index 0000000..9c260e0 --- /dev/null +++ b/authors/apps/notifications/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + name = 'notifications' diff --git a/authors/apps/notifications/cron_job.py b/authors/apps/notifications/cron_job.py new file mode 100644 index 0000000..c4bf4df --- /dev/null +++ b/authors/apps/notifications/cron_job.py @@ -0,0 +1,52 @@ +from django_cron import CronJobBase, Schedule +from django.conf import settings +from django.template.loader import render_to_string +from django.core.mail import EmailMessage + +from .models import Notification, CommentNotification + + +class EmailNotificationCron(CronJobBase): + """Create the cron job for email sending.""" + + RUN_EVERY_MINS = settings.RUN_EVERY_MINS + schedule = Schedule(run_every_mins=RUN_EVERY_MINS) + code = 'authors.apps.notifications.cron_job.EmailNotificationCron' + + def do(self): + """Send emails to all the persons to be notified.""" + subject = 'Authors Haven Notification' + + article_recipients = [] + notifications = Notification.objects.all() + message_template = "article_notification.html" + self.send_notification( + notifications, article_recipients, message_template, subject) + + comment_recipients = [] + notifications = CommentNotification.objects.all() + message_template = "comment_notification.html" + self.send_notification( + notifications, comment_recipients, message_template, subject) + + def send_notification(self, notifications, recipients, message_template, subject): + for notification in notifications: + if not notification.email_sent: + self.get_recipients(notification, recipients) + content = {'notification': notification} + message = render_to_string(message_template, content) + mail = EmailMessage( + subject=subject, + body=message, + to=recipients, + from_email=settings.EMAIL_HOST_USER) + mail.content_subtype = "html" + mail.send(fail_silently=False) + notification.email_sent = True + notification.save() + + def get_recipients(self, notification, recipients): + for user in notification.notified.all(): + if (user not in notification.read.all() + and user.profile.email_notification_enabled): + recipients.append(user.email) diff --git a/authors/apps/notifications/migrations/0001_initial.py b/authors/apps/notifications/migrations/0001_initial.py new file mode 100644 index 0000000..c1048cf --- /dev/null +++ b/authors/apps/notifications/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# Generated by Django 2.1.1 on 2018-10-19 09:45 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('comments', '0007_merge_20181017_1744'), + ('articles', '0012_merge_20181019_1051'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='CommentNotification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('notification', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('classification', models.TextField(default='comment')), + ('email_sent', models.BooleanField(default=False)), + ('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='comments.Comment')), + ('notified', models.ManyToManyField(blank=True, related_name='comment_notified', to=settings.AUTH_USER_MODEL)), + ('read', models.ManyToManyField(blank=True, related_name='comment_read', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('notification', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('classification', models.TextField(default='article')), + ('email_sent', models.BooleanField(default=False)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='articles.Article')), + ('notified', models.ManyToManyField(blank=True, related_name='notified', to=settings.AUTH_USER_MODEL)), + ('read', models.ManyToManyField(blank=True, related_name='read', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/authors/apps/notifications/migrations/__init__.py b/authors/apps/notifications/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authors/apps/notifications/models.py b/authors/apps/notifications/models.py new file mode 100644 index 0000000..2233d16 --- /dev/null +++ b/authors/apps/notifications/models.py @@ -0,0 +1,100 @@ +from django.db import models +from authors.apps.authentication.models import User +from authors.apps.profiles.models import Profile +from authors.apps.articles.models import Article +from authors.apps.comments.models import Comment +from authors.apps.profiles.models import FollowingUser + + +class Notification(models.Model): + """ + Defines fields for notifications. + """ + + class Meta: + # Order notification by time notified + ordering = ['-created_at'] + + # article to send + article = models.ForeignKey(Article, on_delete=models.CASCADE) + # notification message + notification = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + # users to be notified + notified = models.ManyToManyField( + User, related_name='notified', blank=True) + # users that have read + read = models.ManyToManyField(User, related_name='read', blank=True) + classification = models.TextField(default="article") + # check whether email has been sent + email_sent = models.BooleanField(default=False) + + def __str__(self): + "Returns a string representation of notification." + return self.notification + + +def notify_follower(author, notification, article): + """ + Function that adds a notification to the Notification model. + in order to add them to the notified column of the notification. + """ + created_notification = Notification.objects.create( + notification=notification, classification="article", article=article) + + userlist = FollowingUser.objects.filter( + followed_user=author).values_list( + 'following_user', flat=True) + followers = User.objects.filter(id__in=userlist) + + for follower in followers: + # checks if notification is set to True + if follower.profile.app_notification_enabled is True: + created_notification.notified.add(follower.id) + created_notification.save() + + +class CommentNotification(models.Model): + """ + Defines fields for notifications. + """ + + class Meta: + # Order notification by time notified + ordering = ['-created_at'] + + # article to send + comment = models.ForeignKey(Comment, on_delete=models.CASCADE) + # notification message + notification = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + # users to be notified + notified = models.ManyToManyField( + User, related_name='comment_notified', blank=True) + # users that have read + read = models.ManyToManyField( + User, related_name='comment_read', blank=True) + classification = models.TextField(default="comment") + # check whether email has been sent + email_sent = models.BooleanField(default=False) + + def __str__(self): + "Returns a string representation of notification." + return self.notification + + +def notify_comment_follower(article_id, notification, comment): + """ + Function that adds a notification to the Notification model. + in order to add them to the notified column of the notification. + """ + created_notification = CommentNotification.objects.create( + notification=notification, classification="comment", comment=comment) + + followers = Profile.objects.filter(favorites=article_id) + + for follower in followers: + # checks if notification is set to True + if follower.app_notification_enabled is True: + created_notification.notified.add(follower.id) + created_notification.save() diff --git a/authors/apps/notifications/renderers.py b/authors/apps/notifications/renderers.py new file mode 100644 index 0000000..5ba717c --- /dev/null +++ b/authors/apps/notifications/renderers.py @@ -0,0 +1,25 @@ +import json + +from rest_framework.renderers import JSONRenderer + + +class NotificationJSONRenderer(JSONRenderer): + charset = 'utf-8' + + def render(self, data, media_type=None, renderer_context=None): + """ + Check for errors key in data + """ + errors = data.get('errors', None) + + if errors: + """ + We will let the default JSONRenderer handle + rendering errors. + """ + return super(NotificationJSONRenderer, self).render(data) + + # Finally, we can render our data under the "profile" namespace. + return json.dumps({ + 'notification': data + }) diff --git a/authors/apps/notifications/serializers.py b/authors/apps/notifications/serializers.py new file mode 100644 index 0000000..ef44eda --- /dev/null +++ b/authors/apps/notifications/serializers.py @@ -0,0 +1,71 @@ +from rest_framework import serializers + +from .models import Notification, CommentNotification + +from authors.apps.articles.serializers import ArticleSerializer +from authors.apps.comments.serializers import CommentSerializer +from django.utils.timesince import timesince + + +class NotificationSerializer(serializers.ModelSerializer): + article = ArticleSerializer('article') + timestance = serializers.SerializerMethodField( + method_name='calculate_timesince') + unread = serializers.SerializerMethodField(method_name='read') + + class Meta: + """ + Notification fields to be returned to users + """ + model = Notification + fields = ('unread', 'created_at', 'notification', 'classification', + 'article', 'timestance') + + def calculate_timesince(self, instance, now=None): + """ + Get the time difference of the notification with the current time. + """ + return timesince(instance.created_at, now) + + def read(self, instance): + """ + Check if user has read the article. + Returns True or False to the serializer. + """ + request = self.context.get('request') + if request.user in instance.read.all(): + return False + else: + return True + + +class CommentNotificationSerializer(serializers.ModelSerializer): + comment = CommentSerializer('comment') + timestance = serializers.SerializerMethodField( + method_name='calculate_timesince') + unread = serializers.SerializerMethodField(method_name='read') + + class Meta: + """ + Notification fields to be returned to users + """ + model = CommentNotification + fields = ('unread', 'created_at', 'notification', 'classification', + 'comment', 'timestance') + + def calculate_timesince(self, instance, now=None): + """ + Get the time difference of the notification with the current time. + """ + return timesince(instance.created_at, now) + + def read(self, instance): + """ + Check if user has read the article. + Returns True or False to the serializer. + """ + request = self.context.get('request') + if request.user in instance.read.all(): + return False + else: + return True diff --git a/authors/apps/notifications/templates/article_notification.html b/authors/apps/notifications/templates/article_notification.html new file mode 100755 index 0000000..8861d43 --- /dev/null +++ b/authors/apps/notifications/templates/article_notification.html @@ -0,0 +1,201 @@ + + + + + + + Title + + + + + + + + + + + +
+
+ + + + + + +
 
+ + + + + + +
+ + + + + + + +
 
+

AUTHORS HAVEN ARTICLE NOTIFICATION

+ + + + + + +
+ + + + + +
+ + + + + + +
 
+
+
+ + + + + + +
 
+

{{ notification.article.title }}

+ + + + + + +
 
+

{{ notification.notification }}

+ + + + + +
+ + + + +
+
Read Article
+
+
+
+

You're getting this email because you've signed up for email updates. If you + want to opt-out of future emails, unsubscribe here.

+
+
+ + + + + + +
 
+
+
+
+ + + \ No newline at end of file diff --git a/authors/apps/notifications/templates/comment_notification.html b/authors/apps/notifications/templates/comment_notification.html new file mode 100644 index 0000000..8f2cc60 --- /dev/null +++ b/authors/apps/notifications/templates/comment_notification.html @@ -0,0 +1,201 @@ + + + + + + + Title + + + + + + + + + + + +
+
+ + + + + + +
 
+ + + + + + +
+ + + + + + + +
 
+

AUTHORS HAVEN COMMENT NOTIFICATION

+ + + + + + +
+ + + + + +
+ + + + + + +
 
+
+
+ + + + + + +
 
+

{{ notification.comment.body }}

+ + + + + + +
 
+

{{ notification.notification }}

+ + + + + +
+ + + + +
+
Read Comment
+
+
+
+

You're getting this email because you've signed up for email updates. If you + want to opt-out of future emails, unsubscribe here.

+
+
+ + + + + + +
 
+
+
+
+ + + \ No newline at end of file diff --git a/authors/apps/notifications/templates/notification.html b/authors/apps/notifications/templates/notification.html new file mode 100755 index 0000000..efbaca8 --- /dev/null +++ b/authors/apps/notifications/templates/notification.html @@ -0,0 +1,201 @@ + + + + + + + Title + + + + + + + + + + + +
+
+ + + + + + +
 
+ + + + + + +
+ + + + + + + +
 
+

AUTHORS HAVEN NOTIFICATION

+ + + + + + +
+ + + + + +
+ + + + + + +
 
+
+
+ + + + + + +
 
+

{{ notification.article.title }}

+ + + + + + +
 
+

{{ notification.notification }}

+ + + + + +
+ + + + +
+
Read Article
+
+
+
+

You're getting this email because you've signed up for email updates. If you + want to opt-out of future emails, unsubscribe here.

+
+
+ + + + + + +
 
+
+
+
+ + + \ No newline at end of file diff --git a/authors/apps/notifications/templates/static/images/article.jpg b/authors/apps/notifications/templates/static/images/article.jpg new file mode 100755 index 0000000..85e589a Binary files /dev/null and b/authors/apps/notifications/templates/static/images/article.jpg differ diff --git a/authors/apps/notifications/urls.py b/authors/apps/notifications/urls.py new file mode 100644 index 0000000..ece9753 --- /dev/null +++ b/authors/apps/notifications/urls.py @@ -0,0 +1,54 @@ +from django.urls import path +from .views import ( + NotificationDetailsView, + NotificationAPIView, + NotificationSwitchAppAPIView, + NotificationSwitchEmailAPIView, + CommentNotificationAPIView, + CommentNotificationDetailsView, + CommentNotificationSwitchAppAPIView, + CommentNotificationSwitchEmailAPIView, + AllNotificationsAPIView) + + +app_name = 'notifications' + +urlpatterns = [ + # article notification urls + path( + 'articles/', + NotificationDetailsView.as_view(), + name='notification'), + path('articles', NotificationAPIView.as_view(), name='my_notifications'), + path( + 'articles/switch_app/', + NotificationSwitchAppAPIView.as_view(), + name='switch_app_notifications'), + path( + 'articles/switch_email/', + NotificationSwitchEmailAPIView.as_view(), + name='switch_email_notifications'), + + # comment notification urls + path( + 'comments/', + CommentNotificationDetailsView.as_view(), + name='notification'), + path( + 'comments', + CommentNotificationAPIView.as_view(), + name='my_notifications'), + path( + 'comments/switch_app/', + CommentNotificationSwitchAppAPIView.as_view(), + name='switch_app_notifications'), + path( + 'comments/switch_email/', + CommentNotificationSwitchEmailAPIView.as_view(), + name='switch_email_notifications'), + + path('all', AllNotificationsAPIView.as_view(), name='my_notifications'), + + + +] diff --git a/authors/apps/notifications/utils.py b/authors/apps/notifications/utils.py new file mode 100644 index 0000000..5d2c2ff --- /dev/null +++ b/authors/apps/notifications/utils.py @@ -0,0 +1,4 @@ +def merge_two_dicts(x, y): + z = x.copy() + z.update(y) + return z diff --git a/authors/apps/notifications/views.py b/authors/apps/notifications/views.py new file mode 100644 index 0000000..9d913a8 --- /dev/null +++ b/authors/apps/notifications/views.py @@ -0,0 +1,413 @@ +from rest_framework import generics, status +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from django.core.exceptions import ObjectDoesNotExist +from .serializers import NotificationSerializer, CommentNotificationSerializer +from .renderers import NotificationJSONRenderer +from .models import Notification, CommentNotification +from authors.apps.profiles.models import Profile +from .utils import merge_two_dicts + + +class NotificationDetailsView(generics.RetrieveUpdateDestroyAPIView): + """ + get: + delete: + """ + serializer_class = NotificationSerializer + renderer_classes = (NotificationJSONRenderer, ) + permission_classes = (IsAuthenticated, ) + + def get(self, request, pk): + + try: + notification = Notification.objects.get(pk=pk) + serializer = self.serializer_class( + notification, context={'request': request}) + return Response(serializer.data, status.HTTP_200_OK) + except ObjectDoesNotExist: + return Response({ + 'errors': 'Notification does not exist' + }, status.HTTP_404_NOT_FOUND) + + def delete(self, request, pk): + + + try: + notification = Notification.objects.get(pk=pk) + except ObjectDoesNotExist: + return Response({ + 'errors': 'Notification with does not exist' + }, status.HTTP_404_NOT_FOUND) + + # check whether user has the notification before attempting to delete + # it + user = request.user + if user in notification.notified.all(): + notification.notified.remove(user.id) + notification.save() + message = "You have successfully deleted this notification" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + else: + # prevent a user from deleting an notification they do not own + return Response({ + 'errors': 'You cannot delete this notification' + }, status.HTTP_403_FORBIDDEN) + + def put(self, request, pk): + """ + Mark the article notification as read. + """ + try: + notification = Notification.objects.get(pk=pk) + except ObjectDoesNotExist: + return Response({ + 'error': 'Notification with does not exist' + }, status.HTTP_404_NOT_FOUND) + + # check whether user is in the notified field + user = request.user + if user in notification.notified.all(): + notification.read.add(user.id) + notification.save() + message = "You have successfully marked the notification as read" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + else: + return Response({ + 'errors': + 'You cannot mark as read a notification that is not yours' + }, status.HTTP_403_FORBIDDEN) + + +class NotificationAPIView(generics.RetrieveUpdateAPIView): + """ + get: + """ + serializer_class = NotificationSerializer + renderer_classes = (NotificationJSONRenderer, ) + permission_classes = (IsAuthenticated, ) + + def get(self, request): + """ + Retrieve all article notifications from the database for a specific user. + :returns notifications: a json data for the notifications + """ + user = request.user + notifications = Notification.objects.all() + data = {} + + for notification in notifications: + if user in notification.notified.all(): + serializer = self.serializer_class( + notification, context={'request': request}) + data[notification.id] = serializer.data + return Response(data, status=status.HTTP_200_OK) + + def put(self, request): + """ + Mark all article notifications as read. + """ + notifications = Notification.objects.all() + user = request.user + for notification in notifications: + if user in notification.notified.all(): + notification.read.add(user.id) + notification.save() + message = "You successfully marked all notifications as read" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + +""" + +View All notifications + +""" + + +class AllNotificationsAPIView(generics.RetrieveUpdateAPIView): + """ + get: + """ + notification_serializer_class = NotificationSerializer + comment_serializer_class = CommentNotificationSerializer + renderer_classes = (NotificationJSONRenderer, ) + permission_classes = (IsAuthenticated, ) + + def get(self, request): + + user = request.user + article_notifications = Notification.objects.all() + article_data = {} + + for notification in article_notifications: + if user in notification.notified.all(): + serializer = self.notification_serializer_class( + notification, context={'request': request}) + article_data[notification.id] = serializer.data + + notifications = CommentNotification.objects.all() + comment_data = {} + + for notification in notifications: + if user in notification.notified.all(): + serializer = self.comment_serializer_class( + notification, context={'request': request}) + comment_data[notification.id] = serializer.data + + data = merge_two_dicts(article_data, comment_data) + + return Response(data, status=status.HTTP_200_OK) + + +class NotificationSwitchAppAPIView(generics.CreateAPIView): + """ + A user is able to activate or deactivate article notifications. + """ + permission_classes = (IsAuthenticated, ) + serializer_class = NotificationSerializer + + def post(self, request): + """ + This method handles activating and deactivating article notifications. + Checks if the user notification boolean is set to true in + order to deactivate. + Else activates. + """ + user = request.user + profile = Profile.objects.get(user=user) + + if profile.app_notification_enabled is True: + # sets notification boolean in the profile to false + profile.app_notification_enabled = False + profile.save() + message = "You have successfully deactivated in app notifications for articles" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + elif profile.app_notification_enabled is False: + # sets notification boolean in the profile to true + profile.app_notification_enabled = True + profile.save() + message = "You have successfully activated in app notifications for articles" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + +class NotificationSwitchEmailAPIView(generics.CreateAPIView): + """ + A user is able to activate or deactivate article email notifications. + """ + permission_classes = (IsAuthenticated, ) + # queryset = Profile.objects.all() + serializer_class = NotificationSerializer + + def post(self, request): + """ + This method handles activating and deactivating article notifications. + + """ + user = request.user + profile = Profile.objects.get(user=user) + + # notification = request.data.get('email_notification_enabled') + + if profile.email_notification_enabled is True: + # sets notification boolean in the profile to false + profile.email_notification_enabled = False + profile.save() + message = "You have successfully deactivated email notifications" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + elif profile.email_notification_enabled is False: + # sets notification boolean in the profile to true + profile.email_notification_enabled = True + profile.save() + message = "You have successfully activated email notifications" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + +""" Comment Notification View """ + + +class CommentNotificationDetailsView(generics.RetrieveUpdateDestroyAPIView): + """ + get: + delete: + """ + serializer_class = CommentNotificationSerializer + renderer_classes = (NotificationJSONRenderer, ) + permission_classes = (IsAuthenticated, ) + + def get(self, request, pk): + + try: + notification = CommentNotification.objects.get(pk=pk) + serializer = self.serializer_class( + notification, context={'request': request}) + return Response(serializer.data, status.HTTP_200_OK) + except ObjectDoesNotExist: + return Response({ + 'errors': 'Notification does not exist' + }, status.HTTP_404_NOT_FOUND) + + def delete(self, request, pk): + + try: + notification = CommentNotification.objects.get(pk=pk) + except ObjectDoesNotExist: + return Response({ + 'errors': 'Notification with does not exist' + }, status.HTTP_404_NOT_FOUND) + + # check whether user has the notification before attempting to delete + # it + user = request.user + if user in notification.notified.all(): + notification.notified.remove(user.id) + notification.save() + message = "You have successfully deleted this notification" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + else: + # prevent a user from deleting an notification they do not own + return Response({ + 'errors': 'You cannot delete this notification' + }, status.HTTP_403_FORBIDDEN) + + def put(self, request, pk): + """ + Mark the comment notification as read. + """ + try: + notification = CommentNotification.objects.get(pk=pk) + except ObjectDoesNotExist: + return Response({ + 'error': 'Notification with does not exist' + }, status.HTTP_404_NOT_FOUND) + + # check whether user is in the notified field + user = request.user + if user in notification.notified.all(): + notification.read.add(user.id) + notification.save() + message = "You have successfully marked the notification as read" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + else: + return Response({ + 'errors': + 'You cannot mark as read a notification that is not yours' + }, status.HTTP_403_FORBIDDEN) + + +class CommentNotificationAPIView(generics.RetrieveUpdateAPIView): + """ + get: + """ + serializer_class = CommentNotificationSerializer + renderer_classes = (NotificationJSONRenderer, ) + permission_classes = (IsAuthenticated, ) + + def get(self, request): + """ + Retrieve all comment notifications from the database for a specific user. + """ + user = request.user + notifications = CommentNotification.objects.all() + data = {} + + for notification in notifications: + if user in notification.notified.all(): + serializer = self.serializer_class( + notification, context={'request': request}) + data[notification.id] = serializer.data + return Response(data, status=status.HTTP_200_OK) + + def put(self, request): + """ + Mark all comment notifications as read. + """ + notifications = CommentNotification.objects.all() + user = request.user + for notification in notifications: + if user in notification.notified.all(): + notification.read.add(user.id) + notification.save() + message = "You successfully marked all notifications as read" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + +class CommentNotificationSwitchAppAPIView(generics.CreateAPIView): + """ + A user is able to activate or deactivate in app comment notifications. + """ + permission_classes = (IsAuthenticated, ) + # queryset = Profile.objects.all() + serializer_class = CommentNotificationSerializer + + def post(self, request): + """ + This method handles activating and deactivating comment notifications. + """ + user = request.user + profile = Profile.objects.get(user=user) + + if profile.app_notification_enabled is True: + # sets notification boolean in the profile to false + profile.app_notification_enabled = False + profile.save() + message = "You have successfully deactivated in app notifications for comments" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + elif profile.app_notification_enabled is False: + # sets notification boolean in the profile to true + profile.app_notification_enabled = True + profile.save() + message = "You have successfully activated in app notifications for comments" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + +class CommentNotificationSwitchEmailAPIView(generics.CreateAPIView): + + """ + A user is able to activate or deactivate comment email 2notifications. + """ + permission_classes = (IsAuthenticated, ) + # queryset = Profile.objects.all() + serializer_class = CommentNotificationSerializer + + def post(self, request): + """ + This method handles activating and deactivating comment notifications. + + """ + user = request.user + profile = Profile.objects.get(user=user) + + # notification = request.data.get('email_notification_enabled') + + if profile.email_notification_enabled is True: + # sets notification boolean in the profile to false + profile.email_notification_enabled = False + profile.save() + message = "You have successfully deactivated email notifications for comments" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) + + elif profile.email_notification_enabled is False: + # sets notification boolean in the profile to true + profile.email_notification_enabled = True + profile.save() + message = "You have successfully activated email notifications for comments" + response = {"message": message} + return Response(response, status=status.HTTP_200_OK) diff --git a/authors/apps/profiles/migrations/0006_auto_20181019_1245.py b/authors/apps/profiles/migrations/0006_auto_20181019_1245.py new file mode 100644 index 0000000..0f82cff --- /dev/null +++ b/authors/apps/profiles/migrations/0006_auto_20181019_1245.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.1 on 2018-10-19 09:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0005_merge_20181017_1744'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='app_notification_enabled', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='profile', + name='email_notification_enabled', + field=models.BooleanField(default=True), + ), + ] diff --git a/authors/apps/profiles/models.py b/authors/apps/profiles/models.py index 9257829..e001772 100755 --- a/authors/apps/profiles/models.py +++ b/authors/apps/profiles/models.py @@ -16,7 +16,8 @@ class Profile(models.Model): following = models.BooleanField(default=False) number_of_articles = models.IntegerField(default=0) favorites = models.ManyToManyField('articles.Article', related_name='favorited_by') - + app_notification_enabled = models.BooleanField(default=True) + email_notification_enabled = models.BooleanField(default=True) def __str__(self): return self.user.username diff --git a/authors/apps/profiles/serializers.py b/authors/apps/profiles/serializers.py index b270bd4..8b21155 100755 --- a/authors/apps/profiles/serializers.py +++ b/authors/apps/profiles/serializers.py @@ -12,7 +12,8 @@ class ProfileSerializer(serializers.ModelSerializer): class Meta: model = Profile fields = ('username', 'first_name', 'last_name', 'bio', 'image', - 'following', 'number_of_articles', 'created_at', 'updated_at') + 'following', 'number_of_articles', 'created_at', + 'updated_at', 'app_notification_enabled','email_notification_enabled') read_only_fields = ('username',) diff --git a/authors/apps/profiles/utils.py b/authors/apps/profiles/utils.py deleted file mode 100644 index 47c8eb3..0000000 --- a/authors/apps/profiles/utils.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.contrib.auth.models import User -from .models import FollowingUser - - -def get_followed_user(user): - """ Returns a list of users that the given user follows. """ - - userlist = FollowingUser.objects.filter(following_user=user).values_list('followed_user', - flat=True) - return User.objects.filter(id__in=userlist) - - -def get_following_user(user): - """ Returns a list of users that follow the given user. """ - - userlist = FollowingUser.objects.filter(followed_user=user).values_list('following_user', - flat=True) - return User.objects.filter(id__in=userlist) - - -def get_following_each_other(user): - """ - Returns a list of users that a given user follows and they follow the user back. - """ - user_follows = FollowingUser.objects.filter(following_user=user).values_list('followed_user', - flat=True) - user_followed = FollowingUser.objects.filter(followed_user=user).values_list('following_user', - flat=True) - return User.objects.filter( - id__in=set(user_follows).intersection(set(user_followed))) diff --git a/authors/apps/profiles/views.py b/authors/apps/profiles/views.py index e3eeea8..3971dc8 100755 --- a/authors/apps/profiles/views.py +++ b/authors/apps/profiles/views.py @@ -10,9 +10,6 @@ from rest_framework.exceptions import PermissionDenied, ValidationError import jwt from decouple import config, Csv - -from .utils import (get_followed_user, get_following_user, - get_following_each_other) from authors.apps.authentication.models import User @@ -81,16 +78,7 @@ class RetriveProfilesView(RetrieveAPIView): serializer_class = ProfilesSerializers def retrieve(self, request, *args, **kwargs): - - try: - - profiles = Profile.objects.all() - - except Profile.DoesNotExist: - raise ProfileDoesNotExist - - - + profiles = Profile.objects.all() serializer = self.serializer_class(profiles, many=True) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/authors/settings/defaults.py b/authors/settings/defaults.py index 2eb9e01..e173f06 100644 --- a/authors/settings/defaults.py +++ b/authors/settings/defaults.py @@ -56,9 +56,14 @@ 'authors.apps.articles', 'rest_framework_swagger', 'authors.apps.comments', + 'authors.apps.notifications', + 'taggit', 'taggit_serializer', 'django_filters', + 'django_cron', + 'debug_toolbar', + ] @@ -71,6 +76,7 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', ] ROOT_URLCONF = 'authors.urls' @@ -168,3 +174,26 @@ } +DEBUG_TOOLBAR_PANELS = [ + 'debug_toolbar.panels.versions.VersionsPanel', + 'debug_toolbar.panels.timer.TimerPanel', + 'debug_toolbar.panels.settings.SettingsPanel', + 'debug_toolbar.panels.headers.HeadersPanel', + 'debug_toolbar.panels.request.RequestPanel', + 'debug_toolbar.panels.sql.SQLPanel', + 'debug_toolbar.panels.staticfiles.StaticFilesPanel', + 'debug_toolbar.panels.templates.TemplatesPanel', + 'debug_toolbar.panels.cache.CachePanel', + 'debug_toolbar.panels.signals.SignalsPanel', + 'debug_toolbar.panels.logging.LoggingPanel', + 'debug_toolbar.panels.redirects.RedirectsPanel', +] + +INTERNAL_IPS = ('127.0.0.1',) + +RUN_EVERY_MINS = 1 + +CRON_CLASSES = [ + "authors.apps.notifications.cron_job.EmailNotificationCron", +] + diff --git a/authors/urls.py b/authors/urls.py index 7985e2c..1359e4d 100644 --- a/authors/urls.py +++ b/authors/urls.py @@ -18,10 +18,12 @@ from django.views.generic.base import RedirectView from rest_framework.documentation import include_docs_urls from rest_framework_swagger.views import get_swagger_view +import debug_toolbar schema_view = get_swagger_view(title="Authors Haven API ") + urlpatterns = [ path('admin/', admin.site.urls), @@ -30,9 +32,12 @@ path('api/', include('authors.apps.profiles.urls', namespace='profiles')), path('api/', include('authors.apps.articles.urls', namespace='articles')), path('api/articles/', include('authors.apps.comments.urls', namespace='comments')), + path('api/notifications/', include('authors.apps.notifications.urls', namespace='notifications')), path('', RedirectView.as_view(url='coreapi-docs/'), name='index'), path('swagger-docs/', schema_view), path('coreapi-docs/', include_docs_urls(title='Authors Haven API')), - + path('__debug__/', include(debug_toolbar.urls)), + ] + diff --git a/requirements.txt b/requirements.txt index 8b963c6..961a71c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,8 @@ django-rest-swagger==2.2.0 coreapi==2.3.3 sendgrid==5.6.0 django-taggit==0.23.0 -django-taggit-serializer==0.1.7 \ No newline at end of file +django-taggit-serializer==0.1.7 +celery==4.2.1 +django-redis==4.9.0 +django-debug-toolbar==1.10.1 +django-cron==0.5.1 diff --git a/tests/test_notifications/__init__.py b/tests/test_notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_notifications/test_notifications.py b/tests/test_notifications/test_notifications.py new file mode 100644 index 0000000..df98719 --- /dev/null +++ b/tests/test_notifications/test_notifications.py @@ -0,0 +1,766 @@ +from rest_framework.test import APITestCase, APIClient +from authors.apps.authentication.models import User +from tests.test_authentication.test_base import BaseTest +from rest_framework import status +from authors.apps.notifications.utils import merge_two_dicts + + +class Base(APITestCase, BaseTest): + def setUp(self): + BaseTest.__init__(self) + self.client = APIClient() + + def create_login_user(self): + user = User.objects.create_user( + self.username, self.email, self.password) + User.is_verified = True + token = str(user.token(1)) + self.loginresponse = self.client.post( + "/api/users/login/", self.user_login, format="json") + self.addcredentials(token) + + def addcredentials(self, response): + self.client.credentials( + HTTP_AUTHORIZATION='Token ' + response) + + def test_retrieve_notifications(self): + """ Test a user can retrieve all notifications """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.get( + "/api/notifications/all", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_comment_notifications(self): + """ Test a user can retrieve all notifications """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.get( + "/api/notifications/comments", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_single_notification(self): + """ Test a user can retrieve a single notification """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + notification = self.client.get( + "/api/notifications/articles", format="json") + pk = [*notification.data][0] + response = self.client.get( + "/api/notifications/articles", kwargs={'pk': pk}, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_comment_notification_doesnot_exist(self): + """ Test a user can retrieve a single notification """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.get( + f"/api/notifications/comments/{500}", format="json") + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertIn("Notification does not exist", str(response.data)) + + def test_get_article_notification_doesnot_exist(self): + """ Test a user can retrieve a single notification """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.get( + f"/api/notifications/articles/{500}", format="json") + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete_comment_notification_doesnot_exist(self): + """ Test a user can delete a notification that doesnot exist """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.delete( + f"/api/notifications/comments/{500}", format="json") + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete_article_notification_doesnot_exist(self): + """ Test a user can retrieve a single notification """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.delete( + f"/api/notifications/articles/{500}", format="json") + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_mark_as_read_article_notification_doesnot_exist(self): + """ Test a user can retrieve a single notification """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.put( + f"/api/notifications/articles/500", format="json") + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_mark_as_read_comment_notification_doesnot_exist(self): + """ Test a user can retrieve a single notification """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.put( + f"/api/notifications/comments/500", format="json") + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_successfully_delete_notification(self): + """ + Tests that a user can delete a notification. + """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + notification = self.client.get( + "/api/notifications/comments", format="json") + pk = [*notification.data][0] + response = self.client.get( + f"/api/notifications/comments/{pk}", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_comment_notification(self): + """ Test a user can delete comment notification """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + notification = self.client.get( + "/api/notifications/comments", format="json") + pk = [*notification.data][0] + response = self.client.delete( + f"/api/notifications/comments/{pk}", format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_delete_comment_notification_when_not_owner(self): + self.set_up_comment_notifications() + + email = 'user2@user2.com' + password = '12345678' + user = { + "user": { + "email": email, + "password":password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + notification = self.client.get( + "/api/notifications/comments", format="json") + pk = [*notification.data][0] + self.loginresponse = self.client.post( + "/api/users/login/", self.user_login, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.delete( + f"/api/notifications/comments/{pk}", format="json") + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_article_notification_when_not_owner(self): + self.set_up_notifications() + + email = 'user2@user2.com' + password = '12345678' + user = { + "user": { + "email": email, + "password":password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + notification = self.client.get( + "/api/notifications/articles", format="json") + pk = [*notification.data][0] + self.loginresponse = self.client.post( + "/api/users/login/", self.user_login, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.delete( + f"/api/notifications/articles/{pk}", format="json") + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + + def test_mark_article_notification_as_read_when_not_owner(self): + self.set_up_notifications() + + email = 'user2@user2.com' + password = '12345678' + user = { + "user": { + "email": email, + "password":password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + notification = self.client.get( + "/api/notifications/articles", format="json") + pk = [*notification.data][0] + self.loginresponse = self.client.post( + "/api/users/login/", self.user_login, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.put( + f"/api/notifications/articles/{pk}", format="json") + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_mark_comment_notification_as_read_when_not_owner(self): + self.set_up_comment_notifications() + + email = 'user2@user2.com' + password = '12345678' + user = { + "user": { + "email": email, + "password":password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + notification = self.client.get( + "/api/notifications/comments", format="json") + pk = [*notification.data][0] + self.loginresponse = self.client.post( + "/api/users/login/", self.user_login, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + response = self.client.put( + f"/api/notifications/comments/{pk}", format="json") + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + + def test_delete_article_notification(self): + """ Test a user can delete article notification """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + notification = self.client.get( + "/api/notifications/articles", format="json") + pk = [*notification.data][0] + response = self.client.delete( + f"/api/notifications/articles/{pk}", format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_successfully_deactivate_article_app_notification(self): + """ + Tests that a user successfully deactivate in app comment notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + self.client.post( + "/api/notifications/articles/switch_app/", format="json") + response = self.client.post( + "/api/notifications/articles/switch_app/", format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_successfully_activate_article_app_notification(self): + """ + Tests that a user successfully activate in app comment notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/articles/switch_app/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + + def test_successfully_deactivate_articles_email_notification(self): + """ + Tests that a user successfully activate comment email notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/articles/switch_email/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You have successfully deactivated email notifications", str(response.data)) + + + def test_successfully_activate_articles_email_notification(self): + """ + Tests that a user successfully deactivate comment email notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/articles/switch_email/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You have successfully deactivated email notifications", str(response.data)) + + def test_successfully_mark_article_notification_as_read(self): + """ + Tests that a user can mark all article notifications as read. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.put( + "/api/notifications/articles", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You successfully marked all notifications as read" , str(response.data)) + + + def test_successfully_mark_comment_notifications_as_read(self): + """ + Tests that a user can mark all article notifications as read. + """ + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.put( + "/api/notifications/comments", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You successfully marked all notifications as read" , str(response.data)) + + def test_successfully_mark_single_comment_notification_as_read(self): + """ Test a user can mark a single comment_notification as read""" + self.set_up_comment_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + notification = self.client.get( + "/api/notifications/comments", format="json") + pk = [*notification.data][0] + response = self.client.put( + "/api/notifications/comments", kwargs={'pk': pk}, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You successfully marked all notifications as read" , str(response.data)) + + def test_successfully_mark_single_article_notification_as_read(self): + """ Test a user can mark a single notification as read""" + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + notification = self.client.get( + "/api/notifications/articles", format="json") + pk = [*notification.data][0] + response = self.client.put( + "/api/notifications/articles", kwargs={'pk': pk}, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + + def test_successfully_deactivate_comment_app_notification(self): + """ + Tests that a user successfully activate in app comment notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/comments/switch_app/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You have successfully deactivated in app notifications for comments", + str(response.data)) + + + def test_successfully_activate_comment_app_notification(self): + """ + Tests that a user successfully deactivate in app comment notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/comments/switch_app/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You have successfully deactivated in app notifications for comments", + str(response.data)) + + + def test_successfully_deactivate_comment_email_notification(self): + """ + Tests that a user successfully activate comment email notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/comments/switch_email/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("You have successfully deactivated email notifications for comments", + str(response.data)) + + def test_successfully_activate_comment_email_notification(self): + """ + Tests that a user successfully deactivate comment email notifications. + """ + self.set_up_notifications() + + self.email = 'user2@user2.com' + self.password = '12345678' + self.user = { + "user": { + "email": self.email, + "password": self.password + } + } + self.loginresponse = self.client.post( + "/api/users/login/", self.user, format="json") + token = self.loginresponse.data['token'] + self.addcredentials(token) + + response = self.client.post( + "/api/notifications/comments/switch_email/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("'You have successfully deactivated email notifications for comments", + str(response.data)) + + + def test_merge_two_dicts(self): + + dict1 = {'a': 1, 'b': 2} + dict2 = {'c': 3, 'd': 4} + self.assertEqual(merge_two_dicts(dict1, dict2), { + 'a': 1, 'b': 2, 'c': 3, 'd': 4}) + + def set_up_notifications(self): + # create user 1 + user = User.objects.create_user( + self.username, self.email, self.password) + user.is_verified = True + user1token = user.token(1) + user.save() + # create user 2 + # login him in + self.username = 'user223' + self.email = 'user2@user2.com' + self.password = '12345678' + user = User.objects.create_user( + self.username, self.email, self.password) + user.is_verified = True + user.save() + + token = user.token(1) + self.addcredentials(token) + # make him user 2 follow user 1 + + self.client.post('/api/profiles/{}/follow/'.format('simon')) + + # login user 1 and make him post an article + self.addcredentials(user1token) + self.client.post('/api/articles/', self.create_article, format="json") + + def set_up_comment_notifications(self): + # create user one + user = User.objects.create_user( + self.username, self.email, self.password) + user.is_verified = True + user1token = user.token(1) + user.save() + # create user two + self.username = 'user223' + self.email = 'user2@user2.com' + self.password = '12345678' + user = User.objects.create_user( + self.username, self.email, self.password) + user.is_verified = True + user.save() + + token = user.token(1) + + # log user one in + # make user one create an article + self.addcredentials(user1token) + + self.client.post('/api/articles/', self.create_article, format="json") + # log in user two + # make user two favorite an article of user one + self.addcredentials(token) + self.client.post( + '/api/articles/how-to-tnnrain-your-flywwwwwwwwwwf/favorite/', + format="json") + # log in user one + # make him comment on his article + self.addcredentials(user1token) + self.client.post( + '/api/articles/how-to-tnnrain-your-flywwwwwwwwwwf/comments/', + self.test_comment, + format="json") diff --git a/tests/test_profiles/test_user_profile.py b/tests/test_profiles/test_user_profile.py index a45100d..52f49e0 100644 --- a/tests/test_profiles/test_user_profile.py +++ b/tests/test_profiles/test_user_profile.py @@ -34,8 +34,27 @@ def test_update_user_profile_new_email(self): """"This method tests updating a user profile with a missing attribute""" response = self.client.put("/api/profiles/admin/", self.profile_data_4, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_user_profile_doesnot_exist(self): + """"This method tests setting up a new profile""" + response = self.client.get("/api/profiles/dfsfdsfdsd/", format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + def test_retrieve_profiles(self): + response = self.client.get("/api/profiles/", format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + def update_invalid_username(self): + self.client.put("/api/profiles/admin/", self.profile_data, format="json") + response = self.client.put("/api/profiles/admin/", self.profile_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def update_when_not_owner(self): + self.client.credentials( + HTTP_AUTHORIZATION='Token ' + self.sign_up1.data["token"]) + response=self.client.put("/api/profiles/admin/", self.profile_data, format="json") + self.assertEqual(response.status_code, status.HTTT_403_FORBIDDEN) +