diff --git a/codedigger/lists/admin.py b/codedigger/lists/admin.py index 771e176..524c538 100644 --- a/codedigger/lists/admin.py +++ b/codedigger/lists/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import List, ListInfo, Solved, ListExtraInfo, LadderStarted, Enrolled +from .models import List, ListInfo, Solved, ListExtraInfo, LadderStarted, Enrolled, Editor class ListInfoAdmin(admin.ModelAdmin): @@ -26,9 +26,14 @@ class EnrolledAdmin(admin.ModelAdmin): search_fields = ('enroll__user', 'enroll__list') +class EditorAdmin(admin.ModelAdmin): + search_fields = ('editor__user', 'editor__list') + + admin.site.register(List, ListAdmin) admin.site.register(ListInfo, ListInfoAdmin) admin.site.register(Solved, SolvedAdmin) admin.site.register(ListExtraInfo, ListExtraInfoAdmin) admin.site.register(LadderStarted, LadderStartedAdmin) admin.site.register(Enrolled, EnrolledAdmin) +admin.site.register(Editor, EditorAdmin) diff --git a/codedigger/lists/fixtures/editor.json b/codedigger/lists/fixtures/editor.json new file mode 100644 index 0000000..2405584 --- /dev/null +++ b/codedigger/lists/fixtures/editor.json @@ -0,0 +1,10 @@ +[ + { + "model": "lists.editor", + "pk": 1, + "fields": { + "editor_user": 1, + "editor_list": 3 + } + } +] \ No newline at end of file diff --git a/codedigger/lists/fixtures/lists.json b/codedigger/lists/fixtures/lists.json index ea63160..428b0de 100644 --- a/codedigger/lists/fixtures/lists.json +++ b/codedigger/lists/fixtures/lists.json @@ -30,12 +30,25 @@ "pk": 3, "fields": { "name": "testinglist_userlist", - "owner": 1, + "owner": 3, "isAdmin": false, "isTopicWise": true, "type_list": "3", "public": false, "slug": "testinglist_userlist" } + }, + { + "model": "lists.list", + "pk": 4, + "fields": { + "name": "testinglist_userlist1", + "owner": 1, + "isAdmin": false, + "isTopicWise": true, + "type_list": "3", + "public": false, + "slug": "testinglist_userlist1" + } } ] diff --git a/codedigger/lists/fixtures/user.json b/codedigger/lists/fixtures/user.json index 1790586..1e998fa 100644 --- a/codedigger/lists/fixtures/user.json +++ b/codedigger/lists/fixtures/user.json @@ -28,5 +28,20 @@ "created_at": "2021-10-1T10:00:00.511Z", "updated_at": "2021-10-1T10:00:00.511Z" } + }, + { + "model": "user.user", + "pk": 3, + "fields": { + "username": "testinguser2", + "email": "testinguser2@gmail.com", + "password": "QWERTY@123", + "is_verified": true, + "is_active": true, + "is_staff": false, + "auth_provider": "email", + "created_at": "2021-10-1T10:00:00.511Z", + "updated_at": "2021-10-1T10:00:00.511Z" + } } ] diff --git a/codedigger/lists/migrations/0008_editormodel.py b/codedigger/lists/migrations/0008_editormodel.py new file mode 100644 index 0000000..25d1065 --- /dev/null +++ b/codedigger/lists/migrations/0008_editormodel.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.8 on 2022-01-14 12:17 + +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), + ('lists', '0007_auto_20211227_1032'), + ] + + operations = [ + migrations.CreateModel( + name='EditorModel', + fields=[ + ('id', + models.AutoField(auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('editor_list', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='editor_list', + to='lists.list')), + ('editor_user', + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, + related_name='editor_user', + to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/codedigger/lists/migrations/0009_rename_editormodel_editor.py b/codedigger/lists/migrations/0009_rename_editormodel_editor.py new file mode 100644 index 0000000..fe90617 --- /dev/null +++ b/codedigger/lists/migrations/0009_rename_editormodel_editor.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.8 on 2022-01-14 12:18 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('lists', '0008_editormodel'), + ] + + operations = [ + migrations.RenameModel( + old_name='EditorModel', + new_name='Editor', + ), + ] diff --git a/codedigger/lists/models.py b/codedigger/lists/models.py index d8aef1a..9f4ef89 100644 --- a/codedigger/lists/models.py +++ b/codedigger/lists/models.py @@ -144,3 +144,16 @@ class Enrolled(models.Model): def __str__(self): return self.enroll_list.slug + + +class Editor(models.Model): + editor_user = models.ForeignKey(User, + on_delete=models.CASCADE, + related_name="editor_user") + editor_list = models.ForeignKey(List, + on_delete=models.CASCADE, + related_name="editor_list") + + def __str__(self): + return str(self.editor_user) + " can edit list " + str( + self.editor_list) diff --git a/codedigger/lists/serializers.py b/codedigger/lists/serializers.py index e595c54..669245f 100644 --- a/codedigger/lists/serializers.py +++ b/codedigger/lists/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers, status -from .models import ListInfo, Solved, List, ListInfo, LadderStarted +from .models import ListInfo, Solved, List, ListInfo, LadderStarted, Editor from problem.models import Problem from user.models import User, Profile from drf_writable_nested.serializers import WritableNestedModelSerializer @@ -107,7 +107,8 @@ class GetUserlistSerializer(serializers.ModelSerializer): class Meta: model = List - fields = ('id', 'name', 'description', 'slug', 'public') + fields = ('id', 'name', 'description', 'slug') + # fields = ('id', 'name', 'description', 'slug', 'public') class CreateUserlistSerializer(serializers.ModelSerializer): @@ -208,3 +209,12 @@ class EnrollInListSerializer(serializers.Serializer): class Meta: fields = ('slug', ) + + +class EditorListSerializer(serializers.Serializer): + slug = serializers.CharField(required=True) + friend = serializers.CharField(required=True) + + class Meta: + model = Editor + fields = ('slug', 'friend') \ No newline at end of file diff --git a/codedigger/lists/tests/test_lists.py b/codedigger/lists/tests/test_lists.py index 2d00c67..5d2e455 100644 --- a/codedigger/lists/tests/test_lists.py +++ b/codedigger/lists/tests/test_lists.py @@ -38,7 +38,7 @@ def test_check_owner_can_change_visibility_view(self): res2.status_code, 400) def test_check_restrict_change_visibility_view(self): - slug = "testinglist_userlist" + slug = "testinglist_userlist1" test_url = reverse('userlist-edit', kwargs={'slug': slug}) here = User.objects.get(username="testing") here.set_password(self.user_data['password']) @@ -84,11 +84,13 @@ def test_check_only_owner_can_add_problems_view(self): 'platform': 'A' } res2 = client2.post(test_url, data2, format="json") - self.assertEqual(res.status_code, 200) and self.assertEqual( - res2.status_code, 400) + self.assertEqual(res.status_code, 200) and \ + self.assertEqual(res.data['result'],"Given problem has been added to the list") and \ + self.assertEqual(res2.status_code, 400) and \ + self.assertRaises(ValidationException, res) def test_check_owner_can_change_visibility_view(self): - slug = "testinglist_userlist" + slug = "testinglist_userlist1" test_url = reverse('problem-publiclist', kwargs={'slug': slug}) here = User.objects.get(username="testing") here.set_password(self.user_data['password']) @@ -146,26 +148,38 @@ def test_get_user_stats(self): res.data['result'][0]['problems_solved'], res.data['result'][1]['problems_solved']) + def test_add_editors(self): + slug = "testinglist_topicwise" + friend = "testinguser" + username = "testing" + test_url = reverse('add-users') + token = self.login(self.client, self.login_url, self.user_data) + client = self.get_authenticated_client(token) + res = client.post(test_url, { + "slug": slug, + "friend": friend + }, + format="json") + self.assertEqual(res.data['result'], "User has been added to the list") -def test_get_user_list(self): - username = "testing" - test_url = reverse('user-list', kwargs={'username': username}) - here = User.objects.get(username="testing") - here.set_password(self.user_data['password']) - here.save() - res = self.client.post(self.login_url, self.user_data, format="json") - token = res.data['tokens']['access'] - client = APIClient() - client.credentials(HTTP_AUTHORIZATION='Bearer ' + token) - res = client.get(test_url, format="json") + def test_get_user_list(self): + username = "testing" + test_url = reverse('user-list', kwargs={'username': username}) + here = User.objects.get(username="testing") + here.set_password(self.user_data['password']) + here.save() + res = self.client.post(self.login_url, self.user_data, format="json") + token = res.data['tokens']['access'] + client = APIClient() + client.credentials(HTTP_AUTHORIZATION='Bearer ' + token) + res = client.get(test_url, format="json") - username2 = "testing1" - test_url = reverse('user-list', kwargs={'username': username2}) - client = APIClient() - client.credentials(HTTP_AUTHORIZATION='Bearer ' + token) - res2 = client.get(test_url, format="json") + username2 = "testing1" + test_url = reverse('user-list', kwargs={'username': username2}) + client = APIClient() + client.credentials(HTTP_AUTHORIZATION='Bearer ' + token) + res2 = client.get(test_url, format="json") - self.assertEqual(res.status_code, 200) and self.assertRaises( - ValidationException, res2) - if (len(res.data['result']) > 0): - self.assertEqual(res.data['result'][0]['public'], True) + self.assertEqual(res.status_code, 200) and self.assertEqual( + len(res.data['result']), 3) and self.assertRaises( + ValidationException, res2) diff --git a/codedigger/lists/tests/test_setup.py b/codedigger/lists/tests/test_setup.py index f00ce8b..ba249b1 100644 --- a/codedigger/lists/tests/test_setup.py +++ b/codedigger/lists/tests/test_setup.py @@ -12,7 +12,7 @@ class TestSetUp(APITestCase): fixtures = [ "user.json", "problems.json", "lists.json", "list_info.json", - "enrolled.json", "solved.json", "userfriends.json" + "enrolled.json", "solved.json", "userfriends.json", "editor.json" ] @classmethod diff --git a/codedigger/lists/urls.py b/codedigger/lists/urls.py index d2e986a..fac9064 100644 --- a/codedigger/lists/urls.py +++ b/codedigger/lists/urls.py @@ -20,6 +20,7 @@ SearchUserlistView, ListGetView, EnrollListView, + UserListEdit, testing) urlpatterns = [ @@ -69,5 +70,6 @@ path('/stats/standing', UserStandingStats.as_view(), name='user-standing'), + path('add', UserListEdit.as_view(), name='add-users'), path('testing', testing), ] diff --git a/codedigger/lists/views.py b/codedigger/lists/views.py index 22b2ae9..7c11244 100644 --- a/codedigger/lists/views.py +++ b/codedigger/lists/views.py @@ -1,13 +1,13 @@ from django.http import JsonResponse from .cron import updater from rest_framework import generics, status, views, response -from .models import List, ListExtraInfo, LadderStarted, ListInfo, Enrolled +from .models import List, ListExtraInfo, LadderStarted, ListInfo, Enrolled, Editor from problem.models import Problem from .serializers import (GetLadderSerializer, GetSerializer, GetUserlistSerializer, EditUserlistSerializer, CreateUserlistSerializer, ProblemSerializer, UserlistAddSerializer, AddProblemsAdminSerializer, - EnrollInListSerializer) + EnrollInListSerializer, EditorListSerializer) from django.db.models import Q, Subquery, Count from user.permissions import * from user.exception import * @@ -391,25 +391,37 @@ def get_queryset(self): class ListGetView(generics.ListAPIView): - permission_classes = [AuthenticatedOrReadOnly] + permission_classes = [] serializer_class = GetUserlistSerializer - def get_queryset(self): + def get_queryset(self, type): username = self.kwargs['username'] try: user = User.objects.get(username=username) except: raise ValidationException('User with given Username not exists.') - - if self.request.user.is_authenticated and username == self.request.user.username: - qs = List.objects.filter(owner=self.request.user) - else: + if (type == "public"): qs = List.objects.filter(Q(owner=user) & Q(public=True)) - return qs + return qs + elif (type == "private"): + if self.request.user.is_authenticated and username == self.request.user.username: + qs = List.objects.filter( + Q(owner=self.request.user) & Q(public=False)) + return qs + else: + list_ids = Editor.objects.filter( + editor_user=user).values('editor_list') + qs = List.objects.filter(id__in=list_ids) + return qs def get(self, request, username): - qs = self.get_queryset() - send_data = GetUserlistSerializer(qs, many=True).data + send_data = {"public": [], "private": [], "shared": []} + type_list = ["public", "private", "shared"] + + for i in type_list: + qs = self.get_queryset(i) + send_data[i] = GetUserlistSerializer(qs, many=True).data + return response.Response({'status': 'OK', 'result': send_data}) @@ -507,8 +519,13 @@ def post(self, request, *args, **kwargs): "Problem with the given prob_id and platform already exists within the list" ) if curr_list.owner != here: - raise ValidationException( - "Only the owner of the list can add problems to this list") + try: + can_edit = Editor.objects.get(editor_list=curr_list, + editor_user=here) + except: + raise ValidationException( + "The user can not add problems to the list") + listinfo = ListInfo() listinfo.p_list = curr_list listinfo.problem = curr_prob @@ -818,6 +835,47 @@ def post(self, request, *args, **kwargs): status=status.HTTP_201_CREATED) +class UserListEdit(generics.GenericAPIView): + serializer_class = EditorListSerializer + + def post(self, request, *args, **kwargs): + data = request.data + here = self.request.user + list = data.get('slug') + friend = data.get('friend') + + try: + curr_list = List.objects.get(slug=list) + except: + raise ValidationException( + "List with the provided slug does not exist") + try: + friend_id = User.objects.get(username=friend) + except: + raise ValidationException('User with given Username not exists.') + + if curr_list.owner != here: + raise ValidationException("User is not Owner of the list") + + try: + is_friend = UserFriends.objects.get( + Q(from_user=here) & Q(to_user=friend_id)) + except: + raise ValidationException("Users are not friends") + if is_friend.status != 1: + raise ValidationException('Users are not friends') + + Editor.objects.get_or_create(editor_user=friend_id, + editor_list=curr_list) + + return response.Response( + { + "status": 'OK', + 'result': "User has been added to the list" + }, + status=status.HTTP_201_CREATED) + + def testing(request): updater() return JsonResponse({'status': 'OK'}) diff --git a/docs/routes/doc.html b/docs/routes/doc.html index c8b7f0e..e591ab4 100755 --- a/docs/routes/doc.html +++ b/docs/routes/doc.html @@ -80,6 +80,16 @@

Codeforces

Lists

+ +
+
+ POST +
+ + /lists/add + +
+
diff --git a/docs/routes/lists/AddUserToEditTheList.html b/docs/routes/lists/AddUserToEditTheList.html new file mode 100644 index 0000000..f3bc948 --- /dev/null +++ b/docs/routes/lists/AddUserToEditTheList.html @@ -0,0 +1,197 @@ + + + + + + + + + + Codedigger REST API DOC - add + + + + + + + + +
+
+
+ +
+
+

Title

+

To add the user's friends to edit the list +

+
+
+

URL

+
+
+ GET +
+ + /lists/add + +
+
+
+

Headers

+

+

+ + + + + + + + + + + + + +
Header NameRequiredTypeDescription
Authorizationtruestring + Bearer {Token} +
+
+
+

URL Parameters

+

+ For GET Request. + If URL params exist, specify them in accordance with name mentioned in URL section. + Mention if is required or optional. Document data constraints. +

+ + + + + + + + + + + + + + + + + + + +
Param NameRequiredTypeDescription
friendtruestring + friend=username of the friend that need's edit access. +
slugtruestring + slug=list name that the user wants to give edit access. +
+
+
+

Success Response

+

+ Return the staus of the request along with message. +

+

+ Status Code: 200 +

+

+ Sample Response: +

+
+            
+            	{
+                    "status": "OK",
+                    "result": "User has been added to the list"
+                }
+            
+          
+
+
+

Error Response

+

+ Reason of Error: If the user is not Authentication. +

+

+ Status Code: 400 BAD REQUEST +

+

+ Error Response: +

+
+            
+                {
+                    "status": "FAILED",
+                    "error": "Authentication credentials were not provided"
+                }
+            
+          
+
+
+

Sample Call

+

+ Just a sample call to your endpoint in a runnable format + ($.ajax call or a curl request) - this makes life easier and more predictable. +

+
+            
+              curl -X POST "https://api.codedigger.tech/lists/add"
+            
+          
+
+
+

Notes

+

+ This is where all uncertainties, commentary, discussion etc.., can go. + Mention timestamp and identify yourself when leaving comments here. +

+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/routes/lists/EditorAccessList.html b/docs/routes/lists/EditorAccessList.html new file mode 100644 index 0000000..13f8c6e --- /dev/null +++ b/docs/routes/lists/EditorAccessList.html @@ -0,0 +1,262 @@ + + + + + + + + + Codedigger REST API DOC - Editor Access to List + + + + + + + +
+ +
+ +
+
+

Add Problem to List

+

+ Adding a problem to a list is handled by this API call. +

+
+
+

URL

+
+
+ POST +
+ + /lists/userlist/add + +
+
+
+

Headers

+ + + + + + + + + + + + + +
Header NameRequiredTypeDescription
Authorizationtruestring + Bearer {Token} +
+
+

Data Params

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Param NameRequiredTypeDescription
prob_idtruestring + id of the problem +
slugtruestring + slug of the list problem is added to +
platformtruestring + platform of the problem +
descriptionfalsestring + description of the problem +
+
+
+

Success Response

+

+ Status Code: 200 +

+

+ Sample Response: +

+
+            
+    {
+        "status": "OK",
+        "result": "Given problem has been added to the list"
+    }
+            
+          
+
+
+

Error Response 1

+

+ Only authorized users can add problems to any list. +

+

+ Status Code: 401 UNAUTHORIZED +

+

+ Error Response: +

+
+            
+    {
+        "status": "FAILED",
+        "error": "Authentication credentials were not provided"
+    }
+            
+          
+

Error Response 2

+

+ Problem ID, List ID(Slug), Platform and Description are required fields. +

+ Status Code: 400 BAD REQUEST +

+

+ Error Response: +

+
+            
+    {
+        "status": "FAILED",
+        "error": "prob_id or slug or platform or description not provided"
+    }
+            
+          
+

Error Response 3

+

+ The problem ID and platform should be valid. +

+ Status Code: 400 BAD REQUEST +

+

+ Error Response: +

+
+            
+    {
+        "status": "FAILED",
+        "error": "Problem with the given prob_id and platform does not exist"
+    }
+            
+          
+

Error Response 4

+

+ The List ID(slug) should be valid and should exist. +

+ Status Code: 400 BAD REQUEST +

+

+ Error Response: +

+
+            
+    {
+        "status": "FAILED",
+        "error": "List with the provided slug does not exist"
+    }
+            
+          
+

Error Response 5

+

+ The same problem cannot be added to the list twice. +

+ Status Code: 400 BAD REQUEST +

+

+ Error Response: +

+
+            
+    {
+        "status": "FAILED",
+        "error": "Problem with the given prob_id and platform already exists within the list"
+    }
+            
+          
+
+
+

Sample Call

+
+            
+    curl -X POST "http://api.codedigger.tech/lists/userlist/add" \
+    -H "accept: application/json" \
+    -H "Authorization: Bearer {Access Token}" \
+    -H "Content-Type: application/json" \
+    -d "{  \"prob_id\": \"{prob_id}\",  \"slug\": \"{slug}\",  \"platform\": \"{platform}\",  \"description\": \"{description}\"}"
+            
+          
+
+
+

Notes

+

+

+
+
+
+ + + + + + + diff --git a/docs/routes/lists/UserlistAddProblemView.html b/docs/routes/lists/UserlistAddProblemView.html index 4fd180c..13d4cf0 100644 --- a/docs/routes/lists/UserlistAddProblemView.html +++ b/docs/routes/lists/UserlistAddProblemView.html @@ -43,9 +43,10 @@

Codedigger {API} <
  • @sreelayavuyyuru
  • @shivamsinghal1
  • +
  • @jyothiprakash

-

Last Updated: Oct 26, 2021

+

Last Updated: Jan 24, 2022