Skip to content
This repository has been archived by the owner on Jun 16, 2021. It is now read-only.

Completed both the tasks #16

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

I have completed the following tasks

- [ ] Basic endpoints
- [ ] Collaborator feature
- [x] Basic endpoints
- [x] Collaborator feature
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ gunicorn = "*"
drf-yasg = "*"

[requires]
python_version = "3.8"
python_version = "3.6"
4 changes: 2 additions & 2 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions api/migrations/0003_todo_collaborators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.0.6 on 2020-05-12 14:37

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='collaborators', to=settings.AUTH_USER_MODEL),
),
]
20 changes: 20 additions & 0 deletions api/migrations/0004_auto_20200512_2008.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.0.6 on 2020-05-12 14:38

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('api', '0003_todo_collaborators'),
]

operations = [
migrations.AlterField(
model_name='todo',
name='collaborators',
field=models.ManyToManyField(null=True, related_name='collaborators', to=settings.AUTH_USER_MODEL),
),
]
20 changes: 20 additions & 0 deletions api/migrations/0005_auto_20200512_2010.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.0.6 on 2020-05-12 14:40

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('api', '0004_auto_20200512_2008'),
]

operations = [
migrations.AlterField(
model_name='todo',
name='collaborators',
field=models.ManyToManyField(related_name='collaborators', to=settings.AUTH_USER_MODEL),
),
]
2 changes: 1 addition & 1 deletion api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
class Todo(models.Model):
creator = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)

collaborators = models.ManyToManyField(User,related_name='collaborators')
def __str__(self):
return self.title
8 changes: 8 additions & 0 deletions api/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from rest_framework import permissions

class IsOwnerOrCollaborator(permissions.BasePermission):
"""
custom permission to allow only creator and collaborator of a todo to view, edit and delete it.
"""
def has_object_permission(self, request, view, obj):
return (obj.creator == request.user) or (request.user in obj.collaborators.all())
26 changes: 24 additions & 2 deletions api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import Todo

from django.contrib.auth.models import User

"""
TODO:
Expand All @@ -14,7 +14,6 @@ class TodoCreateSerializer(serializers.ModelSerializer):
TODO:
Currently, the /todo/create/ endpoint returns only 200 status code,
after successful Todo creation.

Modify the below code (if required), so that this endpoint would
also return the serialized Todo data (id etc.), alongwith 200 status code.
"""
Expand All @@ -23,7 +22,30 @@ 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 UserObjectSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = ['username']

class TodoSerializer(serializers.ModelSerializer):
collaborators = UserObjectSerializer(many=True, required = False)
creator = UserObjectSerializer(required = False)
class Meta:
model = Todo
fields = ['id', 'title', 'creator', 'collaborators',]
class TodoUpateSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('id', 'title',)
class CollaboratorSerializer(serializers.ModelSerializer):
collaborators = UserObjectSerializer(many=True)
class Meta:
model = Todo
fields = ['collaborators']
10 changes: 8 additions & 2 deletions api/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from django.urls import path
from .views import TodoCreateView
from .views import TodoCreateView, TodoViewSet, CollaboratorViewSet

"""
TODO:
Add the urlpatterns of the endpoints, required for implementing
Todo GET (List and Detail), PUT, PATCH and DELETE.
"""
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'^todo', TodoViewSet)
router.register(r'^todo', CollaboratorViewSet)

urlpatterns = [
path('todo/create/', TodoCreateView.as_view()),
]
]
urlpatterns+=router.urls
123 changes: 116 additions & 7 deletions api/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from rest_framework.authtoken.models import Token
from rest_framework import generics
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from .serializers import TodoCreateSerializer
from .serializers import TodoCreateSerializer, TodoSerializer, CollaboratorSerializer, TodoUpateSerializer
from rest_framework import viewsets
from .models import Todo


from django.contrib.auth.models import User
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework import mixins
from .permissions import IsOwnerOrCollaborator
"""
TODO:
Create the appropriate View classes for implementing
Expand All @@ -18,7 +23,6 @@ class TodoCreateView(generics.GenericAPIView):
TODO:
Currently, the /todo/create/ endpoint returns only 200 status code,
after successful Todo creation.

Modify the below code (if required), so that this endpoint would
also return the serialized Todo data (id etc.), alongwith 200 status code.
"""
Expand All @@ -30,6 +34,111 @@ def post(self, request):
Creates a Todo entry for the logged in user.
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(status=status.HTTP_200_OK)
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
return Response({
'id':instance.id,
'title':instance.title,
}, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)

class TodoViewSet(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
permission_classes = (permissions.IsAuthenticated, IsOwnerOrCollaborator)
queryset = Todo.objects.all()
serializer_class = TodoSerializer
def get_serializer_class(self):
if self.action == 'update' or self.action == 'partial_update':
return TodoUpateSerializer
return TodoSerializer
def list(self, request, *args, **kwargs):
queryset1 = Todo.objects.filter(creator=request.user)
queryset2 = Todo.objects.filter(collaborators=request.user)
queryset = (queryset1 | queryset2).distinct()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)


class CollaboratorViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = CollaboratorSerializer
@action(methods=['put'], detail=True, permission_classes=[permissions.IsAuthenticated, ], url_path='add-collaborator', url_name='add_collaborator')
def add_collaborator(self, request, pk=None):
try:
todo = Todo.objects.get(pk=pk)
except Todo.DoesNotExist:
return Response({'Error':'Invalid Todo Id'}, status=status.HTTP_400_BAD_REQUEST)
else:
if request.user == todo.creator:
try:
collborators = request.data["collaborators"]
except KeyError:
return Response({'Error':'Collaborator field not specified in request'}, status = status.HTTP_400_BAD_REQUEST)
else:
collab_list = []
b=False
for collab in collborators:
try:
collab_name = collab['username']
except KeyError:
return Response({'Error':'No username provided'}, status = status.HTTP_400_BAD_REQUEST)
else:
try:
new_collab = User.objects.get(username=collab_name)
except User.DoesNotExist:
return Response({'Invalid username':collab_name}, status=status.HTTP_400_BAD_REQUEST)
if new_collab and new_collab != todo.creator:
collab_list.append(new_collab)
else:
b=True
todo.collaborators.add(*collab_list)
todo.save()
if b:
return Response({'Denied':'Creator can not be added as collaborator'}, status=status.HTTP_206_PARTIAL_CONTENT)
else:
return Response({'Success':'Added collaborators successfully'}, status=status.HTTP_200_OK)

else:
raise PermissionDenied("You are not the creator of this Todo.")
@action(methods=['patch'], detail=True, permission_classes=[permissions.IsAuthenticated, ], url_path='delete-collaborator', url_name='delete_collaborator')
def delete_collaborator(self, request, pk=None):
try:
todo = Todo.objects.get(pk=pk)
except Todo.DoesNotExist:
return Response({"Error":'Invalid Todo Id'}, status=status.HTTP_400_BAD_REQUEST)
if request.user == todo.creator:
try:
collborators = request.data["collaborators"]
except KeyError:
return Response({'Error':'Collaborator field not specified in request'}, status = status.HTTP_400_BAD_REQUEST)
error_list = []
collab_list = []
collab_obj_list = todo.collaborators.all()
for collab in collab_obj_list:
collab_list.append(collab.username)
for collab in collborators:
try:
collab_name = collab['username']
except KeyError:
return Response({'Error':'No username provided'}, status = status.HTTP_400_BAD_REQUEST)
try:
collab_rem_obj = User.objects.get(username=collab_name)
except User.DoesNotExist:
return Response({'Invalid username':collab_name}, status=status.HTTP_400_BAD_REQUEST)
if (collab_name in collab_list) and (collab_rem_obj):
todo.collaborators.remove(collab_rem_obj)
collab_list.remove(collab_name)
todo.save()
else:
error_list.append(collab_name)
if len(error_list):
return Response({"Following name(s) were not collaborating to the task":error_list}, status = status.HTTP_206_PARTIAL_CONTENT)
else:
return Response({"success":'Removed all the specified collaborators'}, status=status.HTTP_204_NO_CONTENT)
else:
raise PermissionDenied("You are not the creator of this Todo.")

27 changes: 27 additions & 0 deletions authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.0.6 on 2020-05-10 11:38

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
('authtoken', '0002_auto_20160226_1747'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=150)),
('token', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='authtoken.Token')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
16 changes: 16 additions & 0 deletions authentication/migrations/0002_delete_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 3.0.6 on 2020-05-11 06:18

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('authentication', '0001_initial'),
]

operations = [
migrations.DeleteModel(
name='Profile',
),
]
Loading