diff --git a/api/admin.py b/api/admin.py index 8df206b..1f16479 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin -from .models import Todo +from .models import Todo, collaboratorOfTodo -admin.site.register(Todo) \ No newline at end of file +admin.site.register(Todo) +admin.site.register(collaboratorOfTodo) \ No newline at end of file diff --git a/api/migrations/0003_collaboratoroftodo.py b/api/migrations/0003_collaboratoroftodo.py new file mode 100644 index 0000000..94c3989 --- /dev/null +++ b/api/migrations/0003_collaboratoroftodo.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2021-08-07 08:22 + +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', '0002_todo_creator'), + ] + + operations = [ + migrations.CreateModel( + name='collaboratorOfTodo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('collaborator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('todo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Todo')), + ], + ), + ] diff --git a/api/migrations/0004_collaboratoroftodo_collaborator_username.py b/api/migrations/0004_collaboratoroftodo_collaborator_username.py new file mode 100644 index 0000000..1641fe1 --- /dev/null +++ b/api/migrations/0004_collaboratoroftodo_collaborator_username.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2021-08-07 10:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_collaboratoroftodo'), + ] + + operations = [ + migrations.AddField( + model_name='collaboratoroftodo', + name='collaborator_username', + field=models.CharField(default='', max_length=500), + ), + ] diff --git a/api/models.py b/api/models.py index ab3ca0a..397752d 100644 --- a/api/models.py +++ b/api/models.py @@ -7,4 +7,13 @@ class Todo(models.Model): title = models.CharField(max_length=255) def __str__(self): - return self.title \ No newline at end of file + return self.title + + +class collaboratorOfTodo(models.Model): + todo = models.ForeignKey(Todo, on_delete=models.CASCADE) + collaborator = models.ForeignKey(User, on_delete=models.CASCADE) + collaborator_username = models.CharField(max_length=500, default = "") + + def __str__(self): + return self.todo.title \ No newline at end of file diff --git a/api/serializers.py b/api/serializers.py index ffd9d3a..30885b5 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers -from .models import Todo +from .models import Todo, collaboratorOfTodo +from django.contrib.auth.models import User """ @@ -7,6 +8,10 @@ Create the appropriate Serializer class(es) for implementing Todo GET (List and Detail), PUT, PATCH and DELETE. """ +class TodoItemSerializer(serializers.ModelSerializer): + class Meta: + model = Todo + fields = ('id', 'title',) class TodoCreateSerializer(serializers.ModelSerializer): @@ -23,7 +28,34 @@ def save(self, **kwargs): user = self.context['request'].user title = data['title'] todo = Todo.objects.create(creator=user, title=title) - + return todo + class Meta: model = Todo fields = ('id', 'title',) + + + +class TodoCollaboratorSerializer(serializers.ModelSerializer): + + def create(self, **kwargs): + data = self.validated_data + task = self.context["todo"] + if (collaboratorOfTodo.objects.filter(todo = task, collaborator_username = data['collaborator_username']).exists()): + return None + + else: + todoCollaborator = collaboratorOfTodo.objects.create(todo = task, collaborator_username = data['collaborator_username'], collaborator = User.objects.get(username = data['collaborator_username'])) + return todoCollaborator + + class Meta: + model = collaboratorOfTodo + fields = ('id', 'collaborator_username',) + + +class UserAsCollaboraterSerializer(serializers.ModelSerializer): + + class Meta: + model = collaboratorOfTodo + fields = ('todo',) + diff --git a/api/urls.py b/api/urls.py index 72f4978..906b52f 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,TodoGetAllView,TodoByIdView,TodoCollaboratorView, RemoveAllCollaboratorView,removeACollaboratorView, userAsCollaboratorView """ TODO: @@ -9,4 +9,10 @@ urlpatterns = [ path('todo/create/', TodoCreateView.as_view()), + path('todo/', TodoByIdView.as_view()), + path('todo/', TodoGetAllView.as_view()), + path('todo//collaborators', TodoCollaboratorView.as_view()), + path('todo/user_as_collaborator', userAsCollaboratorView.as_view()), + path('todo//remove_all_collaborators', RemoveAllCollaboratorView.as_view()), + path('todo/remove_a_collaborator_by_its_id/', removeACollaboratorView.as_view()), ] \ No newline at end of file diff --git a/api/views.py b/api/views.py index d676c64..d3e6f94 100644 --- a/api/views.py +++ b/api/views.py @@ -2,8 +2,9 @@ from rest_framework import permissions from rest_framework import status from rest_framework.response import Response -from .serializers import TodoCreateSerializer -from .models import Todo +from .serializers import TodoCreateSerializer, TodoItemSerializer, TodoCollaboratorSerializer, UserAsCollaboraterSerializer +from .models import Todo, collaboratorOfTodo +from django.http import HttpResponse """ @@ -12,6 +13,105 @@ Todo GET (List and Detail), PUT, PATCH and DELETE. """ +class TodoByIdView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoItemSerializer + + def get_object(self,user, id): + try: + # t = Todo.objects.get(id = id) + # if t is None: + # return Response({"Todo with the given id does not exist."},status = status.HTTP_404_NOT_FOUND) + return Todo.objects.get(id = id, creator =user) + except: + return None + + def get(self, request, id = None): + todo = self.get_object(request.user, id) + if todo is None: + t = Todo.objects.get(id = id) + if collaboratorOfTodo.objects.filter(todo = t, collaborator = request.user).exists(): + todo = t + else: + return Response({"You do not have permission to view this todo."},status = status.HTTP_400_BAD_REQUEST) + if todo is not None: + serializer = self.get_serializer(todo) + return Response(serializer.data, status = status.HTTP_200_OK) + + + + def put(self, request, id): + todo = self.get_object(request.user, id) + if todo is None: + t = Todo.objects.get(id = id) + if collaboratorOfTodo.objects.filter(todo = t, collaborator = request.user).exists(): + todo = t + else: + return Response({"You do not have permission to update this todo."},status = status.HTTP_400_BAD_REQUEST) + if todo is not None: + serializer = self.get_serializer(todo, data = request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + + + + def patch(self, request, id = None): + todo = self.get_object(request.user, id) + if todo is None: + t = Todo.objects.get(id = id) + if collaboratorOfTodo.objects.filter(todo = t, collaborator = request.user).exists(): + todo = t + else: + return Response({"You do not have permission to update this todo."},status = status.HTTP_400_BAD_REQUEST) + if todo is not None: + serializer = self.get_serializer(todo, data = request.data, partial =True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + + + + def delete(self, request, id = None): + todo = self.get_object(request.user, id) + if todo is None: + t = Todo.objects.get(id = id) + if collaboratorOfTodo.objects.filter(todo = t, collaborator = request.user).exists(): + todo = t + else: + return Response({"You do not have permission to delete this todo."},status = status.HTTP_400_BAD_REQUEST) + if todo is not None: + todo.delete() + return Response(status = status.HTTP_204_NO_CONTENT) + + + +class TodoGetAllView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoItemSerializer + + + def get(self,request): + allTodos = Todo.objects.filter(creator = request.user) + todosDueToCollaboration = collaboratorOfTodo.objects.filter(collaborator = request.user) + serializer_collaborator = UserAsCollaboraterSerializer(todosDueToCollaboration , many =True) + serializer = self.get_serializer(allTodos, many =True) + + responseBody = [] + + for x in serializer.data: + responseBody.append(x) + + for x in serializer_collaborator.data: + todo = Todo.objects.get(id = x['todo']) + creatorOfTodo = todo.creator + responseBody.append({'id' : todo.id , 'title' : todo.title , 'Todo_creator' : creatorOfTodo.username}) + + return Response(responseBody, status = status.HTTP_200_OK) + + + + class TodoCreateView(generics.GenericAPIView): """ @@ -31,5 +131,102 @@ 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) + data = serializer.save() + responseBody = { + 'id' : data.id, + 'title' : data.title + } + return Response(responseBody, status=status.HTTP_201_CREATED) + + + +class TodoCollaboratorView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoCollaboratorSerializer + + def get_object(self, user,id): + try: + return Todo.objects.get(id = id, creator =user) + except: + return None + + def get(self, request, id): + todo = self.get_object(request.user, id) + if todo is None: + t = Todo.objects.get(id = id) + if collaboratorOfTodo.objects.filter(todo = t, collaborator = request.user).exists(): + todo = t + else: + return Response({"You do not have permission to view collaborators of this todo."},status = status.HTTP_400_BAD_REQUEST) + if todo is not None: + allCollaboratorsOfTodo = collaboratorOfTodo.objects.filter(todo = todo) + serializer = TodoCollaboratorSerializer(allCollaboratorsOfTodo, many =True) + return Response(serializer.data, status = status.HTTP_200_OK) + + def post(self, request, id): + todo = self.get_object(request.user,id) + if todo is not None: + Data = request.data + user = request.user + if(user.username == Data['collaborator_username']): + return Response({"You cannot become the collaborator as you are the creator of this todo"},status = status.HTTP_400_BAD_REQUEST) + else: + serializer = TodoCollaboratorSerializer(data = request.data, context = {"todo" : todo}) + serializer.is_valid(raise_exception=True) + newCollaborator = serializer.create() + if newCollaborator is not None: + return Response(serializer.data) + else: + return Response({"Entered user is already a collaborator of this todo."}, status = status.HTTP_400_BAD_REQUEST) + else: + return Response({"You do not have permission to add collaborators to this todo"},status = status.HTTP_400_BAD_REQUEST) + + + + +class RemoveAllCollaboratorView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoCollaboratorSerializer + + def delete(self, request, id): + todo = Todo.objects.get(id = id) + if (todo.creator != request.user): + return Response({"You do not have permissions to delete collaborators of this todo."},status = status.HTTP_400_BAD_REQUEST) + elif todo is not None: + CollaboratorsToBeDeleted = collaboratorOfTodo.objects.filter(todo = todo) + for c in CollaboratorsToBeDeleted: + c.delete() + return Response({"All Collaborators of this todo deleted successfully"},status = status.HTTP_204_NO_CONTENT) + + + + + +class removeACollaboratorView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = TodoCollaboratorSerializer + + def delete(self, request, id): + CollaboratorToBeDeleted = collaboratorOfTodo.objects.get(id = id) + todo = CollaboratorToBeDeleted.todo + if todo.creator == request.user: + CollaboratorToBeDeleted.delete() + return Response({"Collaborator deleted successfully"},status = status.HTTP_204_NO_CONTENT) + + else: + return Response({"You do not have permissions to delete collaborator of this todo."},status = status.HTTP_400_BAD_REQUEST) + + +class userAsCollaboratorView(generics.GenericAPIView): + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = UserAsCollaboraterSerializer + + def get(self, request): + todos = collaboratorOfTodo.objects.filter(collaborator = request.user) + serializer = UserAsCollaboraterSerializer(todos , many =True) + responseBody = [] + for x in serializer.data: + todo = Todo.objects.get(id = x['todo']) + creatorOfTodo = todo.creator + responseBody.append({'Todo_id' : todo.id , 'Todo_title' : todo.title , 'Todo_creator' : creatorOfTodo.username}) + return Response(responseBody) diff --git a/authentication/serializers.py b/authentication/serializers.py index 47264af..3c13db3 100644 --- a/authentication/serializers.py +++ b/authentication/serializers.py @@ -2,22 +2,45 @@ from django.contrib.auth import authenticate from django.contrib.auth.models import User - class TokenSerializer(serializers.Serializer): token = serializers.CharField(max_length=500) class LoginSerializer(serializers.Serializer): + # TODO: Implement login functionality - pass + username = serializers.CharField(max_length=500) + password = serializers.CharField(max_length=500) + + def verify(self, validated_data): + data = validated_data + user = authenticate(username = data['username'], password = data['password']) + if user is not None: + return user + else: + return None class RegisterSerializer(serializers.Serializer): # TODO: Implement register functionality - pass + name = serializers.CharField(max_length=500) + email = serializers.CharField(max_length=500) + username = serializers.CharField(max_length=500) + password = serializers.CharField(max_length=500) + + def create(self, validated_data): + data = validated_data + + newUser = User.objects.create_user(data['username'],data['email'],data['password']) + newUser.first_name = data['name'] + newUser.save() + return newUser class UserSerializer(serializers.ModelSerializer): # TODO: Implement the functionality to display user details - pass + + class Meta: + model = User + fields = ('id', 'username', 'email', 'first_name') \ No newline at end of file diff --git a/authentication/views.py b/authentication/views.py index d748434..8ed57ba 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -3,8 +3,9 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.authtoken.models import Token -from .serializers import ( - LoginSerializer, RegisterSerializer, UserSerializer, TokenSerializer) +from .serializers import LoginSerializer, RegisterSerializer, UserSerializer, TokenSerializer +from django.contrib.auth.models import User + def create_auth_token(user): @@ -21,7 +22,19 @@ class LoginView(generics.GenericAPIView): Implement login functionality, taking username and password as input, and returning the Token. """ - pass + serializer_class = LoginSerializer + + def post(self , request): + data = request.data + loginSerializer = LoginSerializer(data = data) + loginSerializer.is_valid(raise_exception=True) + user = loginSerializer.verify(data) + if user is not None: + token = create_auth_token(user) + return Response({'token': token.key}) + else: + return Response({"User credentials are incorrect."},status = status.HTTP_400_BAD_REQUEST) + class RegisterView(generics.GenericAPIView): @@ -30,7 +43,26 @@ class RegisterView(generics.GenericAPIView): Implement register functionality, registering the user by taking his details, and returning the Token. """ - pass + + serializer_class = RegisterSerializer + + + def post(self, request): + data = request.data + if(User.objects.filter(email = data['email']).exists()): + return Response({"Email already taken."}, status = status.HTTP_400_BAD_REQUEST) + elif (User.objects.filter(username = data['username']).exists()): + return Response({"Username already taken."}, status = status.HTTP_400_BAD_REQUEST) + else: + serializer = RegisterSerializer(data = data) + serializer.is_valid(raise_exception=True) + user = serializer.create(data) + # print(user) + token = create_auth_token(user) + return Response({'token': token.key}) + + + class UserProfileView(generics.RetrieveAPIView): @@ -39,4 +71,9 @@ class UserProfileView(generics.RetrieveAPIView): Implement the functionality to retrieve the details of the logged in user. """ - pass \ No newline at end of file + serializer_class = UserSerializer + permission_classes = (permissions.IsAuthenticated, ) + def get(self, request): + serializer = UserSerializer(request.user) + data = serializer.data + return Response({'id' : data['id'], 'name' : data['first_name'], 'email' : data['email'], 'username' : data['username']}) \ No newline at end of file diff --git a/todo/settings.py b/todo/settings.py index 8359271..86b2f26 100644 --- a/todo/settings.py +++ b/todo/settings.py @@ -45,7 +45,8 @@ 'rest_framework.authtoken', 'drf_yasg', 'corsheaders', - 'api' + 'api', + 'authentication' ] MIDDLEWARE = [ diff --git a/todo/urls.py b/todo/urls.py index 889fed7..3e1b943 100644 --- a/todo/urls.py +++ b/todo/urls.py @@ -35,6 +35,7 @@ path('admin/', admin.site.urls), path('', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('', include('api.urls')), + path('auth/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('auth/', include('authentication.urls')), path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ]