diff --git a/api/migrations/0003_todo_collabs.py b/api/migrations/0003_todo_collabs.py new file mode 100644 index 0000000..fd47a61 --- /dev/null +++ b/api/migrations/0003_todo_collabs.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.7 on 2021-08-03 07:03 + +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='collabs', + field=models.ManyToManyField(related_name='collaborators', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/api/models.py b/api/models.py index ab3ca0a..eafc8fe 100644 --- a/api/models.py +++ b/api/models.py @@ -5,6 +5,6 @@ class Todo(models.Model): creator = models.ForeignKey(User, on_delete=models.CASCADE) title = models.CharField(max_length=255) - + collabs = models.ManyToManyField(User,related_name='collaborators') def __str__(self): return self.title \ No newline at end of file diff --git a/api/serializers.py b/api/serializers.py index ffd9d3a..7dc7287 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import User from rest_framework import serializers from .models import Todo @@ -23,7 +24,65 @@ def save(self, **kwargs): user = self.context['request'].user title = data['title'] todo = Todo.objects.create(creator=user, title=title) - + data['id'] = todo.id + class Meta: model = Todo fields = ('id', 'title',) + +class TodoSerializer(serializers.ModelSerializer): + class Meta: + model = Todo + fields = ('id','title',) + +class TodoAddCollabSerializer(serializers.Serializer): + username = serializers.CharField() + def validate(self, attrs): + username = attrs.get('username') + todo = self.context.get('todo') + + if username == todo.creator.username: + raise serializers.ValidationError({ + 'Creator': 'Cannot add creator of todo as a collaborator' + }) + + if username not in [i.username for i in User.objects.all()]: + raise serializers.ValidationError({ + 'username': 'Such a user does not exist' + }) + + if username in [i.username for i in todo.collabs.all()]: + raise serializers.ValidationError({ + 'username': 'User is already a collaborator to this todo' + }) + return attrs + + def save(self,todo): + newCollab = User.objects.get(username=self.validated_data.get('username')) + self.context.get('todo').collabs.add(newCollab) + +class TodoRemoveCollabSerializer(serializers.Serializer): + username = serializers.CharField() + def validate(self, attrs): + username = attrs.get('username') + todo = self.context.get('todo') + + if username == todo.creator.username: + raise serializers.ValidationError({ + 'Creator': 'Given user is creator of this todo' + }) + + if username not in [i.username for i in User.objects.all()]: + raise serializers.ValidationError({ + 'username': 'Such a user does not exist' + }) + + if username not in [i.username for i in todo.collabs.all()]: + raise serializers.ValidationError({ + 'username': 'User is not a collaborator to this todo' + }) + return attrs + + def save(self,todo): + newCollab = User.objects.get(username=self.validated_data.get('username')) + self.context.get('todo').collabs.remove(newCollab) diff --git a/api/urls.py b/api/urls.py index 72f4978..ad3b6b7 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 (TodoCreateView,TodoListView,TodoDetail,TodoAddCollabs,TodoRemoveCollabs) """ TODO: @@ -9,4 +9,8 @@ urlpatterns = [ path('todo/create/', TodoCreateView.as_view()), + path('todo/',TodoListView.as_view()), + path('todo//', TodoDetail.as_view()), + path('todo//add-collaborators/',TodoAddCollabs.as_view()), + path('todo//remove-collaborators/',TodoRemoveCollabs.as_view()), ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index d676c64..faf9fb4 100644 --- a/api/views.py +++ b/api/views.py @@ -1,8 +1,9 @@ -from rest_framework import generics -from rest_framework import permissions -from rest_framework import status +from django.db.models import Q +from rest_framework import (generics, permissions, status) +from django.http import Http404 from rest_framework.response import Response -from .serializers import TodoCreateSerializer +from rest_framework.views import APIView +from .serializers import (TodoCreateSerializer, TodoSerializer,TodoAddCollabSerializer,TodoRemoveCollabSerializer) from .models import Todo @@ -32,4 +33,112 @@ 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) + return Response(serializer.data,status=status.HTTP_200_OK) + +class TodoListView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoSerializer + + # PRINTING ALL TODO IN WHICH AUTHENTICATED USER IS CREATOR OR CONTRIBUTOR + def get(self,request): + todos = Todo.objects.filter(Q(creator=request.user) | Q(collabs=request.user)) + serializer = self.get_serializer(todos,many=True) + return Response(serializer.data) + +class TodoDetail(APIView): + permission_classes = (permissions.IsAuthenticated, ) + def get_todo(self, id): + try: + return Todo.objects.get(id=id) + except Todo.DoesNotExist: + raise Http404 + + def check_user(self,todo): + if todo.creator == self.request.user: + return 1 + if self.request.user in todo.collabs.all(): + return 1 + return 0 + + def get(self, request, id): + todo = self.get_todo(id) + if self.check_user(todo)==0: + return Response(status=401) + serializer = TodoSerializer(todo) + return Response(serializer.data) + + def put(self, request, id): + todo = self.get_todo(id) + if self.check_user(todo)==0: + return Response(status=401) + serializer = TodoSerializer(todo, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def patch(self, request, id): + todo = self.get_todo(id) + if self.check_user(todo)==0: + return Response(status=401) + serializer = TodoSerializer(todo, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, id): + todo = self.get_todo(id) + if self.check_user(todo)==0: + return Response(status=401) + todo.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class TodoAddCollabs(APIView): + permission_classes = (permissions.IsAuthenticated, ) + def get_todo(self, id): + try: + return Todo.objects.get(id=id) + except Todo.DoesNotExist: + raise Http404 + + def check_user(self,todo): + if todo.creator == self.request.user: + return 1 + return 0 + + def post(self,request,id): + todo = self.get_todo(id) + if self.check_user(todo)==0: + return Response(status=401) + + serializer = TodoAddCollabSerializer(data = request.data, context={'todo':todo}) + if serializer.is_valid(): + serializer.save(todo) + return Response({'info': 'New Collaborator Added'}) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class TodoRemoveCollabs(APIView): + permission_classes = (permissions.IsAuthenticated, ) + def get_todo(self, id): + try: + return Todo.objects.get(id=id) + except Todo.DoesNotExist: + raise Http404 + + def check_user(self,todo): + if todo.creator == self.request.user: + return 1 + return 0 + + def post(self,request,id): + todo = self.get_todo(id) + if self.check_user(todo)==0: + return Response(status=401) + + serializer = TodoRemoveCollabSerializer(data = request.data, context={'todo':todo}) + if serializer.is_valid(): + serializer.save(todo) + return Response({'info': 'Collaborator Removed'}) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/authentication/serializers.py b/authentication/serializers.py index 47264af..2232798 100644 --- a/authentication/serializers.py +++ b/authentication/serializers.py @@ -1,5 +1,7 @@ +from django.db.models import fields from rest_framework import serializers from django.contrib.auth import authenticate +from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import User @@ -9,15 +11,50 @@ class TokenSerializer(serializers.Serializer): class LoginSerializer(serializers.Serializer): # TODO: Implement login functionality - pass - + username = serializers.CharField() + password = serializers.CharField(style={'input_type': 'password'},trim_whitespace=False) + + def validate(self, attrs): + username = attrs.get('username') + password = attrs.get('password') + + if username and password: + user = authenticate(request=self.context.get('request'),username=username, password=password) + if not user: + msg = _('Unable to log in with provided credentials.') + raise serializers.ValidationError(msg, code='authorization') + else: + msg = _('Must include "username" and "password".') + raise serializers.ValidationError(msg, code='authorization') + + attrs['user'] = user + return attrs + class RegisterSerializer(serializers.Serializer): # TODO: Implement register functionality - pass + first_name = serializers.CharField(max_length=100,required=True) + email = serializers.EmailField(required=True) + username = serializers.CharField(max_length=150,required=True) + password = serializers.CharField( + write_only=True, + required=True, + style={'input_type': 'password', 'placeholder': 'Password'} + ) + + def create(self, validated_data): + return User.objects.create(**validated_data) + class UserSerializer(serializers.ModelSerializer): # TODO: Implement the functionality to display user details - pass + class Meta: + model = User + fields = ['id', 'username', 'first_name', 'email'] + + + + + \ No newline at end of file diff --git a/authentication/views.py b/authentication/views.py index d748434..17e6e32 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,12 +1,12 @@ -from rest_framework import permissions -from rest_framework import generics -from rest_framework import status +from django.contrib.auth.models import User +from rest_framework import permissions, serializers, generics, status +from rest_framework.decorators import permission_classes +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.authtoken.models import Token from .serializers import ( LoginSerializer, RegisterSerializer, UserSerializer, TokenSerializer) - def create_auth_token(user): """ Returns the token required for authentication for a user. @@ -21,7 +21,11 @@ class LoginView(generics.GenericAPIView): Implement login functionality, taking username and password as input, and returning the Token. """ - pass + def post(self,request): + serializer = LoginSerializer(data = request.data) + serializer.is_valid(raise_exception=True) + user = serializer.validated_data['user'] + return Response({ "token": str(create_auth_token(user)) }, status=200) class RegisterView(generics.GenericAPIView): @@ -30,13 +34,34 @@ class RegisterView(generics.GenericAPIView): Implement register functionality, registering the user by taking his details, and returning the Token. """ - pass - + def post(self,request): + serializer = RegisterSerializer(data={ + 'first_name':request.data['name'], + 'email':request.data['email'], + 'username':request.data['username'], + 'password':request.data['password'], + }) + if serializer.is_valid(): + try: + newUser = serializer.save() + except : + return Response({'username': 'This username is already taken.'}, status=400) + return Response({ "token": str(create_auth_token(newUser)) }, status=201) + return Response(serializer.errors,status=400) -class UserProfileView(generics.RetrieveAPIView): +class UserProfileView(generics.GenericAPIView): """ TODO: Implement the functionality to retrieve the details of the logged in user. """ - pass \ No newline at end of file + permission_classes = (permissions.IsAuthenticated, ) + def post(self,request): + user = User.objects.get(id=request.user.id) + serializer = UserSerializer(user) + return Response({ + "id": serializer.data['id'], + "name": serializer.data['first_name'], + "email": serializer.data['email'], + "username": serializer.data['username'] + }) \ No newline at end of file diff --git a/todo/settings.py b/todo/settings.py index 8359271..6ca1fa1 100644 --- a/todo/settings.py +++ b/todo/settings.py @@ -63,9 +63,9 @@ CORS_ORIGIN_ALLOW_ALL = True REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', - ) + ], } ROOT_URLCONF = 'todo.urls'