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 }}
+
+
+ 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 }}
+
+
+ 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 }}
+
+
+ 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)
+