diff --git a/api/migrations/0003_todo_collaborators.py b/api/migrations/0003_todo_collaborators.py new file mode 100644 index 0000000..0cd02df --- /dev/null +++ b/api/migrations/0003_todo_collaborators.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2021-08-07 09:21 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api', '0002_todo_creator'), + ] + + operations = [ + migrations.AddField( + model_name='todo', + name='collaborators', + field=models.ManyToManyField(related_name='collabs', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/api/models.py b/api/models.py index ab3ca0a..0b3fd8a 100644 --- a/api/models.py +++ b/api/models.py @@ -5,6 +5,7 @@ class Todo(models.Model): creator = models.ForeignKey(User, on_delete=models.CASCADE) title = models.CharField(max_length=255) + collaborators = models.ManyToManyField(User, related_name = 'collabs') def __str__(self): return self.title \ No newline at end of file diff --git a/api/serializers.py b/api/serializers.py index ffd9d3a..9efb224 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from .models import Todo - +from django.contrib.auth.models import User """ TODO: @@ -23,7 +23,33 @@ def save(self, **kwargs): user = self.context['request'].user title = data['title'] todo = Todo.objects.create(creator=user, title=title) - + response = { + "id": todo.id, + "title": todo.title, + } + return response + class Meta: model = Todo fields = ('id', 'title',) + +class TodoCommonSerializer(serializers.ModelSerializer): + class Meta: + model = Todo + fields = ('id', 'title',) + +class TodoListSerializer(serializers.ModelSerializer): + class Meta: + model = Todo + fields = ('id', 'title', 'collaborators',) + +class UserCollabSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('username',) + +class TodoCollabSerializer(serializers.ModelSerializer): + collaborators = UserCollabSerializer(read_only=True, many=True) + class Meta: + model = Todo + fields = ('id', 'collaborators',) diff --git a/api/urls.py b/api/urls.py index 72f4978..ce28cce 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import TodoCreateView +from .views import * """ TODO: @@ -9,4 +9,8 @@ urlpatterns = [ path('todo/create/', TodoCreateView.as_view()), + path('todo/', TodoListView.as_view()), + path('todo//', TodoDetailView.as_view()), + path('todo//add-collaborators/', TodoAddCollabView.as_view()), + path('todo//remove-collaborators/', TodoDeleteCollabView.as_view()), ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index d676c64..6901e36 100644 --- a/api/views.py +++ b/api/views.py @@ -2,16 +2,23 @@ from rest_framework import permissions from rest_framework import status from rest_framework.response import Response -from .serializers import TodoCreateSerializer +from .serializers import * from .models import Todo - +from django.contrib.auth.models import User """ TODO: Create the appropriate View classes for implementing Todo GET (List and Detail), PUT, PATCH and DELETE. """ - +class CustomPermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + if(request.method == "POST"): + return True + todo = Todo.objects.get(id=obj.id) + if(todo.creator == request.user or request.user in todo.collaborators.all()): + return True + return False class TodoCreateView(generics.GenericAPIView): """ @@ -31,5 +38,65 @@ def post(self, request): """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) + response = serializer.save() + return Response(response,status=status.HTTP_200_OK) + +class TodoListView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoListSerializer + + def get(self, request): + queryset_cr = Todo.objects.filter(creator = request.user) + queryset_co = Todo.objects.filter(collaborators=request.user) + + serializer_cr = self.get_serializer(queryset_cr,many=True) + serializer_co = self.get_serializer(queryset_co,many=True) + + response = { + "CREATOR" : + serializer_cr.data, + "COLLABORATOR" : + serializer_co.data, + } + return Response(response,status=status.HTTP_200_OK) + + +class TodoDetailView(generics.RetrieveUpdateDestroyAPIView, CustomPermission): + permission_classes = (permissions.IsAuthenticated, CustomPermission) + serializer_class = TodoCommonSerializer + lookup_url_kwarg = 'id' + queryset = Todo.objects.all() + + +class TodoAddCollabView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated,) + serializer_class = TodoCollabSerializer + + def patch(self, request, id): + todo = Todo.objects.get(id=id) + if(request.user == todo.creator): + username = request.data['username'] + user = User.objects.get(username=username) + if(user != request.user): + todo.collaborators.add(user) + serializer_class = self.get_serializer(todo,data=request.data) + serializer_class.is_valid(raise_exception=True) + return Response(serializer_class.data, status = status.HTTP_200_OK) + return Response({"Error": "You are already the creator of this todo"}, status = status.HTTP_400_BAD_REQUEST) + return Response({"Error": "You are not the creator of this todo"}, status = status.HTTP_403_FORBIDDEN) + + +class TodoDeleteCollabView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoCollabSerializer + + def patch(self, request, id): + todo = Todo.objects.get(id=id) + if(request.user == todo.creator): + username = request.data['username'] + user = User.objects.get(username=username) + todo.collaborators.remove(user) + serializer_class = self.get_serializer(todo,data=request.data) + serializer_class.is_valid(raise_exception=True) + return Response(serializer_class.data, status = status.HTTP_200_OK) + return Response({"Error": "You are not the creator of this todo"}, status = status.HTTP_403_FORBIDDEN) \ No newline at end of file diff --git a/authentication/serializers.py b/authentication/serializers.py index 47264af..fda9805 100644 --- a/authentication/serializers.py +++ b/authentication/serializers.py @@ -9,15 +9,40 @@ class TokenSerializer(serializers.Serializer): class LoginSerializer(serializers.Serializer): # TODO: Implement login functionality - pass + def save(self,data): + user = authenticate(username=data['username'], password=data['password']) + if(not user): + raise serializers.ValidationError({'Error': 'Incorrect password or username!'}) + else: + return user + + class Meta: + model = User + fields = ( 'username', 'password',) class RegisterSerializer(serializers.Serializer): # TODO: Implement register functionality - pass + + def save(self, data): + if(User.objects.filter(username=data['username']).exists()): + raise serializers.ValidationError({'Error': 'Username already exists!'}) + else: + user = User.objects.create_user( + first_name = data['name'], + email = data['email'], + username = data['username'], + password = data['password'],) + return user + + class Meta: + model = User + fields = ( 'first_name', 'email', 'username', 'password',) class UserSerializer(serializers.ModelSerializer): # TODO: Implement the functionality to display user details - pass - \ No newline at end of file + name = serializers.CharField(source='first_name') + class Meta: + model = User + fields = ( 'id','name', 'email', 'username',) \ No newline at end of file diff --git a/authentication/views.py b/authentication/views.py index d748434..790d194 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -5,7 +5,8 @@ from rest_framework.authtoken.models import Token from .serializers import ( LoginSerializer, RegisterSerializer, UserSerializer, TokenSerializer) - +from django.contrib.auth import login +from django.contrib.auth.models import User def create_auth_token(user): """ @@ -21,8 +22,19 @@ class LoginView(generics.GenericAPIView): Implement login functionality, taking username and password as input, and returning the Token. """ - pass - + queryset = User.objects.all() + serializer_class = LoginSerializer + + def post(self , request): + serializer_class = self.get_serializer(data=request.data) + serializer_class.is_valid(raise_exception=True) + user = serializer_class.save(request.data) + token = create_auth_token(user) + login(request, user) + response = { + 'token' : token.key + } + return Response(response, status = status.HTTP_200_OK) class RegisterView(generics.GenericAPIView): """ @@ -30,7 +42,20 @@ class RegisterView(generics.GenericAPIView): Implement register functionality, registering the user by taking his details, and returning the Token. """ - pass + queryset = User.objects.all() + serializer_class = RegisterSerializer + + def post(self, request): + serializer_class = self.get_serializer(data=request.data) + serializer_class.is_valid(raise_exception=True) + user = serializer_class.save(request.data) + token = create_auth_token(user) + login(request, user) + response = { + 'token' : token.key + } + return Response(response, status = status.HTTP_200_OK) + class UserProfileView(generics.RetrieveAPIView): @@ -39,4 +64,10 @@ class UserProfileView(generics.RetrieveAPIView): Implement the functionality to retrieve the details of the logged in user. """ - pass \ No newline at end of file + permission_classes = (permissions.IsAuthenticated,) + queryset = User.objects.all() + serializer_class = UserSerializer + def post(self, request): + serializer_class = self.get_serializer(request.user) + return Response(serializer_class.data, status = status.HTTP_200_OK) + \ No newline at end of file