diff --git a/api/admin.py b/api/admin.py index c9e75e2..8578524 100644 --- a/api/admin.py +++ b/api/admin.py @@ -9,3 +9,4 @@ admin.site.register(models.Task) admin.site.register(models.Access) admin.site.register(models.Label) +admin.site.register(models.Notification) diff --git a/api/migrations/0004_notification.py b/api/migrations/0004_notification.py new file mode 100644 index 0000000..5779fd3 --- /dev/null +++ b/api/migrations/0004_notification.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 00:41 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api', '0003_task_done'), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('published_date', models.DateField(verbose_name='Published date')), + ('modification_date', models.DateField(blank=True, null=True, verbose_name='Modification Date')), + ('status', models.IntegerField(default=1, verbose_name='Status')), + ('target_type', models.CharField(max_length=50, verbose_name='Target type')), + ('target_id', models.PositiveIntegerField(verbose_name='Target ID')), + ('target_intention', models.CharField(max_length=50, verbose_name='Target intention')), + ('title', models.CharField(max_length=255, verbose_name='Title')), + ('text', models.TextField(verbose_name='Text')), + ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications_received', to=settings.AUTH_USER_MODEL, verbose_name='Receiver')), + ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_sent', to=settings.AUTH_USER_MODEL, verbose_name='Sender')), + ], + options={ + 'verbose_name_plural': 'Notifications', + }, + ), + ] diff --git a/api/migrations/0005_notification_is_read.py b/api/migrations/0005_notification_is_read.py new file mode 100644 index 0000000..2e4cd53 --- /dev/null +++ b/api/migrations/0005_notification_is_read.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 01:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_notification'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='is_read', + field=models.BooleanField(default=0, verbose_name='Is read'), + ), + ] diff --git a/api/migrations/0006_task_created_by.py b/api/migrations/0006_task_created_by.py new file mode 100644 index 0000000..710d184 --- /dev/null +++ b/api/migrations/0006_task_created_by.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 03:03 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api', '0005_notification_is_read'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='created_by', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='task_created', to=settings.AUTH_USER_MODEL, verbose_name='Created by'), + preserve_default=False, + ), + ] diff --git a/api/migrations/0007_auto_20170323_0350.py b/api/migrations/0007_auto_20170323_0350.py new file mode 100644 index 0000000..0ba56ea --- /dev/null +++ b/api/migrations/0007_auto_20170323_0350.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 03:50 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0006_task_created_by'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='task_created', to=settings.AUTH_USER_MODEL, verbose_name='Created by'), + ), + ] diff --git a/api/migrations/0008_auto_20170323_0351.py b/api/migrations/0008_auto_20170323_0351.py new file mode 100644 index 0000000..0e271dd --- /dev/null +++ b/api/migrations/0008_auto_20170323_0351.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 03:51 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0007_auto_20170323_0350'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='created_by', + field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, related_name='task_created', to=settings.AUTH_USER_MODEL, verbose_name='Created by'), + preserve_default=False, + ), + ] diff --git a/api/migrations/0009_auto_20170323_0352.py b/api/migrations/0009_auto_20170323_0352.py new file mode 100644 index 0000000..19e6881 --- /dev/null +++ b/api/migrations/0009_auto_20170323_0352.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 03:52 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0008_auto_20170323_0351'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='task_created', to=settings.AUTH_USER_MODEL, verbose_name='Created by'), + ), + ] diff --git a/api/migrations/0010_auto_20170323_0353.py b/api/migrations/0010_auto_20170323_0353.py new file mode 100644 index 0000000..df22bd3 --- /dev/null +++ b/api/migrations/0010_auto_20170323_0353.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2017-03-23 03:53 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0009_auto_20170323_0352'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='created_by', + field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, related_name='task_created', to=settings.AUTH_USER_MODEL, verbose_name='Created by'), + preserve_default=False, + ), + ] diff --git a/api/models.py b/api/models.py index dc99797..235bffb 100644 --- a/api/models.py +++ b/api/models.py @@ -4,6 +4,8 @@ from django.db import models from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver RIGHT_ACCESS = ( @@ -107,6 +109,13 @@ class Meta: related_name="tasks" ) + created_by = models.ForeignKey( + User, + verbose_name='Created by', + related_name='task_created', + blank=True + ) + assigned = models.ForeignKey( User, verbose_name='assigned', @@ -255,3 +264,81 @@ class Meta: def __unicode__(self): return self.name + + +class Notification(models.Model): + class Meta: + verbose_name_plural = "Notifications" + + sender = models.ForeignKey( + User, + verbose_name='Sender', + related_name='notification_sent' + ) + + receiver = models.ForeignKey( + User, + verbose_name='Receiver', + related_name='notifications_received' + ) + + published_date = models.DateField( + verbose_name='Published date' + ) + + modification_date = models.DateField( + verbose_name='Modification Date', + blank=True, + null=True + ) + + status = models.IntegerField( + verbose_name='Status', + default=1 + ) + + is_read = models.BooleanField( + verbose_name='Is read', + default=0 + ) + + target_type = models.CharField( + verbose_name='Target type', + max_length=50 + ) + + target_id = models.PositiveIntegerField( + verbose_name='Target ID' + ) + + target_intention = models.CharField( + verbose_name='Target intention', + max_length=50 + ) + + title = models.CharField( + verbose_name='Title', + max_length=255 + ) + + text = models.TextField( + verbose_name='Text', + ) + + +# Receivers +@receiver(post_save, sender=Task, dispatch_uid="task_saved") +def task_saved(sender, instance, created, *args, **kwargs): + # If the user creating the task is not the one assigned + if created and instance.assigned is not None and instance.assigned is not instance.created_by: + Notification.objects.create( + sender=instance.created_by, + receiver=instance.assigned, + published_date=instance.creation_date, + target_type="task", + target_id=instance.id, + target_intention="create", + title='You have been assigned to "%s"' % instance.name, + text=instance.description + ) + diff --git a/api/serializers.py b/api/serializers.py index 5f9d514..cd04d37 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -172,6 +172,15 @@ class Meta: labels = BaseLabelSerializer(many=True, read_only=True) + created_by = UserSerializer(read_only=True) + created_by_id = serializers.PrimaryKeyRelatedField( + queryset=models.User.objects.all(), + source='created_by', + write_only=True, + required=False, + allow_null=True + ) + assigned = UserSerializer(read_only=True) assigned_id = serializers.PrimaryKeyRelatedField( queryset=models.User.objects.all(), @@ -189,6 +198,27 @@ class Meta: ) +class NotificationSerializer(serializers.ModelSerializer): + + class Meta: + model = models.Notification + fields = '__all__' + + sender = UserSerializer(read_only=True) + sender_id = serializers.PrimaryKeyRelatedField( + queryset=models.User.objects.all(), + source='sender', + write_only=True + ) + + receiver = UserSerializer(read_only=True) + receiver_id = serializers.PrimaryKeyRelatedField( + queryset=models.User.objects.all(), + source='receiver', + write_only=True + ) + + class FileSerializer(serializers.ModelSerializer): class Meta: diff --git a/api/urls.py b/api/urls.py index ce2c555..111c7cc 100755 --- a/api/urls.py +++ b/api/urls.py @@ -16,6 +16,17 @@ views.UserRetrieve.as_view(), name='users_detail' ), + # NOTIFICATION + url( + r'^notification$', + views.NotificationList.as_view(), + name='notification_list' + ), + url( + r'^notification/(?P\d+)$', + views.NotificationRetrieveUpdate.as_view(), + name='notification_detail' + ), # PROJECTS url( r'^projects$', diff --git a/api/views.py b/api/views.py index b3dca9f..50a21b6 100755 --- a/api/views.py +++ b/api/views.py @@ -7,6 +7,7 @@ import django_filters from django.shortcuts import get_object_or_404 +from django.shortcuts import get_list_or_404 from rest_framework import generics from rest_framework import filters @@ -32,6 +33,7 @@ def post(self, request, *args, **kwargs): content = { 'token': str(token.key), + 'id': user.id } return Response(content) @@ -170,6 +172,9 @@ class TaskListCreate(generics.ListCreateAPIView): serializer_class = serializers.TaskSerializer filter_class = TaskFilter + def perform_create(self, serializer): + serializer.save(created_by=self.request.user) + def get_queryset(self): return models.Task.objects.filter(project__access__user=self.request.user) @@ -177,9 +182,37 @@ def get_queryset(self): class TaskRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.TaskSerializer + def perform_update(self, serializer): + serializer.save(created_by=self.request.user) + def get_queryset(self): return models.Task.objects.all() +""" +NOTIFICATIONS +""" + + +class NotificationList(generics.ListAPIView): + serializer_class = serializers.NotificationSerializer + + def get_queryset(self): + return models.Notification.objects.filter(receiver__id=self.request.user.id) + + +class NotificationRetrieveUpdate(generics.RetrieveUpdateAPIView): + serializer_class = serializers.NotificationSerializer + + def get_queryset(self): + #Check if user have access to this notification + get_object_or_404( + models.Notification, + receiver__id=self.request.user.id, + pk=self.kwargs['pk'] + ) + + return models.Notification.objects.filter(receiver__id=self.request.user.id,pk=self.kwargs['pk']) + """ LABELS