From aa9be1c5b4d89fbb46067a28d2894795628cdbb9 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sat, 5 Oct 2019 13:08:52 +0700
Subject: [PATCH 01/12] add test to accept only one job and abort the rest
 application

---
 core/tests/test_vacancies.py | 93 ++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/core/tests/test_vacancies.py b/core/tests/test_vacancies.py
index 1667ac14..a2a8b1b9 100644
--- a/core/tests/test_vacancies.py
+++ b/core/tests/test_vacancies.py
@@ -1,5 +1,6 @@
 from datetime import datetime
 
+import json
 import requests_mock
 from django.contrib.auth.models import User
 from rest_framework import status
@@ -433,3 +434,95 @@ class SupervisorApprovalTests(APITestCase):
         response = self.client.patch(url, format='json')
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
         self.assertEqual(new_vacancy.verified, False)
+
+class AcceptOneOfferTests(APITestCase):        
+
+    def generateObject(self):
+        new_user = User.objects.create_user('dummy.company', 'dummy.company@company.com', 'lalala123')
+        new_company = Company.objects.create(user=new_user, description="lalala", status=Company.VERIFIED, logo=None,
+                                             address=None)
+        
+        new_user2 = User.objects.create_user('dummy.company2', 'dummy.company2@company.com', 'lalala123')
+        new_company2 = Company.objects.create(user=new_user2, description="lalala", status=Company.VERIFIED, logo=None,
+                                             address=None)
+
+        new_vacancy = Vacancy.objects.create(company=new_company, verified=True, open_time=datetime.fromtimestamp(0),
+                                             description="lalala", close_time=datetime.today())
+        
+        new_vacancy2 = Vacancy.objects.create(company=new_company2, verified=True, open_time=datetime.fromtimestamp(0),
+                                             description="lalala", close_time=datetime.today())
+
+        new_user3 = User.objects.create_user('dummy.student', 'dummy.student@company.com', 'lalala123')
+        new_student = Student.objects.create(user=new_user3, npm=1234123412)
+
+        return new_user3, new_vacancy, new_vacancy2, new_student
+
+    def test_number_of_content_response_object_given_id_auth(self):
+        
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+        
+        self.client.force_authenticate(new_user3)
+
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content)
+
+        status_response = []
+        for app in body:
+            status_response.append(app['status'])        
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertTrue(len(body) >= 2)
+        self.assertFalse(len(body) == 0)
+        self.assertTrue('new' in status_response)
+        self.assertTrue('aborted' in status_response)
+
+    def test_student_not_exist_given_auth(self):
+        new_user3,new_vacancy, new_vacancy2, new_student = self.generateObject()
+
+        self.client.force_authenticate(new_user3)
+
+        user4 = User.objects.create_user('student_user_4', 'student_user_4@company.com', 'lalala123')
+        other_student = Student.objects.create(user=user4, npm=1098765432)
+
+        Application.objects.create(student=other_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=other_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content)  
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertTrue(len(body) == 0)
+
+    def test_type_error_if_input_null(self):
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+
+        self.client.force_authenticate(new_user3)
+
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        with self.assertRaises(TypeError):
+            url = '/api/acceptoffer/' + None + '/vacancy/' + str(new_vacancy.pk) + '/'
+        
+        with self.assertRaises(TypeError):
+            url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + None + '/'
+    
+    def test_if_requester_is_not_authenticated(self):
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+        
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content) 
+
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-- 
GitLab


From b9dd731de845d73ae795d034ce68de6abd2f4cdf Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sat, 5 Oct 2019 13:12:27 +0700
Subject: [PATCH 02/12] add new endpoint & model attr to accept only one job
 and

---
 core/models/vacancies.py      |  1 +
 core/serializers/vacancies.py |  2 +-
 core/views/vacancies.py       | 20 ++++++++++++++++++++
 kape/urls.py                  |  3 ++-
 4 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/core/models/vacancies.py b/core/models/vacancies.py
index f2b5dc49..8968e626 100644
--- a/core/models/vacancies.py
+++ b/core/models/vacancies.py
@@ -23,6 +23,7 @@ class Application(models.Model):
     BOOKMARKED = 2
     REJECTED = 3
     ACCEPTED = 4
+    ABORTED = 5
 
     cover_letter = models.TextField(null=True, blank=True)
     student = models.ForeignKey(Student, on_delete=models.CASCADE)
diff --git a/core/serializers/vacancies.py b/core/serializers/vacancies.py
index 313f2064..a2bd2e4f 100644
--- a/core/serializers/vacancies.py
+++ b/core/serializers/vacancies.py
@@ -61,7 +61,7 @@ class ApplicationStatusSerializer(serializers.ModelSerializer):
 
 class SupervisorStudentApplicationSerializer(serializers.ModelSerializer):
     def to_representation(self, instance):
-        status_map = ["new", "read", "bookmarked", "rejected", "accepted" ]
+        status_map = ["new", "read", "bookmarked", "rejected", "accepted","aborted" ]
         return {
             'name' : instance.student.full_name,
             'npm' : instance.student.npm,
diff --git a/core/views/vacancies.py b/core/views/vacancies.py
index e4b8b989..12fdbef6 100644
--- a/core/views/vacancies.py
+++ b/core/views/vacancies.py
@@ -275,3 +275,23 @@ class BookmarkedVacancyByStudentViewSet(viewsets.GenericViewSet):
         student.bookmarked_vacancies.remove(vacancy)
         return Response(
             self.serializer_class(student.bookmarked_vacancies, many=True, context={'request': request}).data)
+
+class AcceptOfferByStudentViewSet(MultiSerializerViewSetMixin, viewsets.GenericViewSet):
+    queryset = Application.objects.all()
+    permission_classes = [IsAdminOrStudent]
+
+    def partial_update(self, request, student_id, pk=None):
+        """
+        Get list of a student {student_id}'s cancel offered vacancies
+        ---
+        """
+        student = get_object_or_404(Student.objects.all().order_by('-updated'), pk=student_id)
+        apps = Application.objects.filter(student=student)
+        
+        for a in apps:
+            if a.vacancy_id != int(pk):
+                serializer = ApplicationStatusSerializer(a, data={'status': 5}, partial=True)
+                if serializer.is_valid():
+                    serializer.save()
+        
+        return Response(SupervisorStudentApplicationSerializer(apps, many=True, context={'request': request}).data)
\ No newline at end of file
diff --git a/kape/urls.py b/kape/urls.py
index 21a1d46b..5cb90a79 100755
--- a/kape/urls.py
+++ b/kape/urls.py
@@ -25,7 +25,7 @@ from core import apps
 from core.views.accounts import StudentViewSet, CompanyViewSet, SupervisorViewSet, UserViewSet, LoginViewSet, \
     CompanyRegisterViewSet
 from core.views.vacancies import VacancyViewSet, BookmarkedVacancyByStudentViewSet, StudentApplicationViewSet, \
-    CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet
+    CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet, AcceptOfferByStudentViewSet
 
 schema_view = get_swagger_view()
 router = routers.DefaultRouter()
@@ -38,6 +38,7 @@ router.register(r'register', CompanyRegisterViewSet)
 router.register(r'vacancies', VacancyViewSet)
 router.register(r'applications', ApplicationViewSet)
 # router.register(r'students/(?P<student_id>\d+)/profile', StudentProfileViewSet)
+router.register(r'acceptoffer/(?P<student_id>\d+)/vacancy', AcceptOfferByStudentViewSet)
 router.register(r'students/(?P<student_id>\d+)/bookmarked-vacancies', BookmarkedVacancyByStudentViewSet,
                 base_name='bookmarked-vacancy-list')
 router.register(r'students/(?P<student_id>\d+)/applied-vacancies', StudentApplicationViewSet,
-- 
GitLab


From ea9810a2e8712df42d96fbabd4835e8eef61a9f8 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sat, 5 Oct 2019 13:08:52 +0700
Subject: [PATCH 03/12] add test to accept only one job and abort the rest
 application

---
 core/tests/test_vacancies.py | 93 ++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/core/tests/test_vacancies.py b/core/tests/test_vacancies.py
index d8c654f6..f6ab7938 100644
--- a/core/tests/test_vacancies.py
+++ b/core/tests/test_vacancies.py
@@ -1,6 +1,7 @@
 from datetime import datetime
 from django.utils import timezone
 
+import json
 import requests_mock
 from django.contrib.auth.models import User
 from rest_framework import status
@@ -434,3 +435,95 @@ class SupervisorApprovalTests(APITestCase):
         response = self.client.patch(url, format='json')
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
         self.assertEqual(new_vacancy.verified, False)
+
+class AcceptOneOfferTests(APITestCase):        
+
+    def generateObject(self):
+        new_user = User.objects.create_user('dummy.company', 'dummy.company@company.com', 'lalala123')
+        new_company = Company.objects.create(user=new_user, description="lalala", status=Company.VERIFIED, logo=None,
+                                             address=None)
+        
+        new_user2 = User.objects.create_user('dummy.company2', 'dummy.company2@company.com', 'lalala123')
+        new_company2 = Company.objects.create(user=new_user2, description="lalala", status=Company.VERIFIED, logo=None,
+                                             address=None)
+
+        new_vacancy = Vacancy.objects.create(company=new_company, verified=True, open_time=datetime.fromtimestamp(0),
+                                             description="lalala", close_time=datetime.today())
+        
+        new_vacancy2 = Vacancy.objects.create(company=new_company2, verified=True, open_time=datetime.fromtimestamp(0),
+                                             description="lalala", close_time=datetime.today())
+
+        new_user3 = User.objects.create_user('dummy.student', 'dummy.student@company.com', 'lalala123')
+        new_student = Student.objects.create(user=new_user3, npm=1234123412)
+
+        return new_user3, new_vacancy, new_vacancy2, new_student
+
+    def test_number_of_content_response_object_given_id_auth(self):
+        
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+        
+        self.client.force_authenticate(new_user3)
+
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content)
+
+        status_response = []
+        for app in body:
+            status_response.append(app['status'])        
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertTrue(len(body) >= 2)
+        self.assertFalse(len(body) == 0)
+        self.assertTrue('new' in status_response)
+        self.assertTrue('aborted' in status_response)
+
+    def test_student_not_exist_given_auth(self):
+        new_user3,new_vacancy, new_vacancy2, new_student = self.generateObject()
+
+        self.client.force_authenticate(new_user3)
+
+        user4 = User.objects.create_user('student_user_4', 'student_user_4@company.com', 'lalala123')
+        other_student = Student.objects.create(user=user4, npm=1098765432)
+
+        Application.objects.create(student=other_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=other_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content)  
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertTrue(len(body) == 0)
+
+    def test_type_error_if_input_null(self):
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+
+        self.client.force_authenticate(new_user3)
+
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        with self.assertRaises(TypeError):
+            url = '/api/acceptoffer/' + None + '/vacancy/' + str(new_vacancy.pk) + '/'
+        
+        with self.assertRaises(TypeError):
+            url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + None + '/'
+    
+    def test_if_requester_is_not_authenticated(self):
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+        
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content) 
+
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-- 
GitLab


From e34cc05612e23a1c53fe5914bcec8d3890252ba6 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sat, 5 Oct 2019 13:12:27 +0700
Subject: [PATCH 04/12] add new endpoint & model attr to accept only one job
 and

---
 core/models/vacancies.py      |  1 +
 core/serializers/vacancies.py |  2 +-
 core/views/vacancies.py       | 20 ++++++++++++++++++++
 kape/urls.py                  |  3 ++-
 4 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/core/models/vacancies.py b/core/models/vacancies.py
index f2b5dc49..8968e626 100644
--- a/core/models/vacancies.py
+++ b/core/models/vacancies.py
@@ -23,6 +23,7 @@ class Application(models.Model):
     BOOKMARKED = 2
     REJECTED = 3
     ACCEPTED = 4
+    ABORTED = 5
 
     cover_letter = models.TextField(null=True, blank=True)
     student = models.ForeignKey(Student, on_delete=models.CASCADE)
diff --git a/core/serializers/vacancies.py b/core/serializers/vacancies.py
index 313f2064..a2bd2e4f 100644
--- a/core/serializers/vacancies.py
+++ b/core/serializers/vacancies.py
@@ -61,7 +61,7 @@ class ApplicationStatusSerializer(serializers.ModelSerializer):
 
 class SupervisorStudentApplicationSerializer(serializers.ModelSerializer):
     def to_representation(self, instance):
-        status_map = ["new", "read", "bookmarked", "rejected", "accepted" ]
+        status_map = ["new", "read", "bookmarked", "rejected", "accepted","aborted" ]
         return {
             'name' : instance.student.full_name,
             'npm' : instance.student.npm,
diff --git a/core/views/vacancies.py b/core/views/vacancies.py
index 56165caa..2f805138 100644
--- a/core/views/vacancies.py
+++ b/core/views/vacancies.py
@@ -350,3 +350,23 @@ class BookmarkedVacancyByStudentViewSet(viewsets.GenericViewSet):
         student.bookmarked_vacancies.remove(vacancy)
         return Response(
             self.serializer_class(student.bookmarked_vacancies, many=True, context={'request': request}).data)
+
+class AcceptOfferByStudentViewSet(MultiSerializerViewSetMixin, viewsets.GenericViewSet):
+    queryset = Application.objects.all()
+    permission_classes = [IsAdminOrStudent]
+
+    def partial_update(self, request, student_id, pk=None):
+        """
+        Get list of a student {student_id}'s cancel offered vacancies
+        ---
+        """
+        student = get_object_or_404(Student.objects.all().order_by('-updated'), pk=student_id)
+        apps = Application.objects.filter(student=student)
+        
+        for a in apps:
+            if a.vacancy_id != int(pk):
+                serializer = ApplicationStatusSerializer(a, data={'status': 5}, partial=True)
+                if serializer.is_valid():
+                    serializer.save()
+        
+        return Response(SupervisorStudentApplicationSerializer(apps, many=True, context={'request': request}).data)
\ No newline at end of file
diff --git a/kape/urls.py b/kape/urls.py
index 5ae9c078..ade154c6 100755
--- a/kape/urls.py
+++ b/kape/urls.py
@@ -25,7 +25,7 @@ from core import apps
 from core.views.accounts import StudentViewSet, CompanyViewSet, SupervisorViewSet, UserViewSet, LoginViewSet, \
     CompanyRegisterViewSet
 from core.views.vacancies import VacancyViewSet, BookmarkedVacancyByStudentViewSet, StudentApplicationViewSet, \
-    CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet
+    CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet, AcceptOfferByStudentViewSet
 from core.views.feedbacks import FeedbackViewSet
 
 schema_view = get_swagger_view()
@@ -40,6 +40,7 @@ router.register(r'vacancies', VacancyViewSet)
 router.register(r'applications', ApplicationViewSet)
 router.register(r'feedbacks', FeedbackViewSet)
 # router.register(r'students/(?P<student_id>\d+)/profile', StudentProfileViewSet)
+router.register(r'acceptoffer/(?P<student_id>\d+)/vacancy', AcceptOfferByStudentViewSet)
 router.register(r'students/(?P<student_id>\d+)/bookmarked-vacancies', BookmarkedVacancyByStudentViewSet,
                 base_name='bookmarked-vacancy-list')
 router.register(r'students/(?P<student_id>\d+)/applied-vacancies', StudentApplicationViewSet,
-- 
GitLab


From c5a4a8730df433bc2db50996b0da66cc9cf783c6 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sun, 6 Oct 2019 00:32:37 +0700
Subject: [PATCH 05/12] merge

---
 core/migrations/0015_merge_20191005_2038.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 core/migrations/0015_merge_20191005_2038.py

diff --git a/core/migrations/0015_merge_20191005_2038.py b/core/migrations/0015_merge_20191005_2038.py
new file mode 100644
index 00000000..d4afc1b8
--- /dev/null
+++ b/core/migrations/0015_merge_20191005_2038.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-05 13:38
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0014_feedback'),
+        ('core', '0014_company_category'),
+    ]
+
+    operations = [
+    ]
-- 
GitLab


From 93c84527c135e250ded2eba2f99f2d051f4cb1ea Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sat, 5 Oct 2019 13:08:52 +0700
Subject: [PATCH 06/12] add test to accept only one job and abort the rest
 application

---
 core/tests/test_vacancies.py | 93 ++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/core/tests/test_vacancies.py b/core/tests/test_vacancies.py
index d8c654f6..f6ab7938 100644
--- a/core/tests/test_vacancies.py
+++ b/core/tests/test_vacancies.py
@@ -1,6 +1,7 @@
 from datetime import datetime
 from django.utils import timezone
 
+import json
 import requests_mock
 from django.contrib.auth.models import User
 from rest_framework import status
@@ -434,3 +435,95 @@ class SupervisorApprovalTests(APITestCase):
         response = self.client.patch(url, format='json')
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
         self.assertEqual(new_vacancy.verified, False)
+
+class AcceptOneOfferTests(APITestCase):        
+
+    def generateObject(self):
+        new_user = User.objects.create_user('dummy.company', 'dummy.company@company.com', 'lalala123')
+        new_company = Company.objects.create(user=new_user, description="lalala", status=Company.VERIFIED, logo=None,
+                                             address=None)
+        
+        new_user2 = User.objects.create_user('dummy.company2', 'dummy.company2@company.com', 'lalala123')
+        new_company2 = Company.objects.create(user=new_user2, description="lalala", status=Company.VERIFIED, logo=None,
+                                             address=None)
+
+        new_vacancy = Vacancy.objects.create(company=new_company, verified=True, open_time=datetime.fromtimestamp(0),
+                                             description="lalala", close_time=datetime.today())
+        
+        new_vacancy2 = Vacancy.objects.create(company=new_company2, verified=True, open_time=datetime.fromtimestamp(0),
+                                             description="lalala", close_time=datetime.today())
+
+        new_user3 = User.objects.create_user('dummy.student', 'dummy.student@company.com', 'lalala123')
+        new_student = Student.objects.create(user=new_user3, npm=1234123412)
+
+        return new_user3, new_vacancy, new_vacancy2, new_student
+
+    def test_number_of_content_response_object_given_id_auth(self):
+        
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+        
+        self.client.force_authenticate(new_user3)
+
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content)
+
+        status_response = []
+        for app in body:
+            status_response.append(app['status'])        
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertTrue(len(body) >= 2)
+        self.assertFalse(len(body) == 0)
+        self.assertTrue('new' in status_response)
+        self.assertTrue('aborted' in status_response)
+
+    def test_student_not_exist_given_auth(self):
+        new_user3,new_vacancy, new_vacancy2, new_student = self.generateObject()
+
+        self.client.force_authenticate(new_user3)
+
+        user4 = User.objects.create_user('student_user_4', 'student_user_4@company.com', 'lalala123')
+        other_student = Student.objects.create(user=user4, npm=1098765432)
+
+        Application.objects.create(student=other_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=other_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content)  
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertTrue(len(body) == 0)
+
+    def test_type_error_if_input_null(self):
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+
+        self.client.force_authenticate(new_user3)
+
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        with self.assertRaises(TypeError):
+            url = '/api/acceptoffer/' + None + '/vacancy/' + str(new_vacancy.pk) + '/'
+        
+        with self.assertRaises(TypeError):
+            url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + None + '/'
+    
+    def test_if_requester_is_not_authenticated(self):
+        new_user3, new_vacancy, new_vacancy2, new_student = self.generateObject()
+        
+        Application.objects.create(student=new_student, vacancy=new_vacancy, cover_letter="asdasdasd")
+        Application.objects.create(student=new_student, vacancy=new_vacancy2, cover_letter="asdasdasd")
+
+        url = '/api/acceptoffer/' + str(new_student.pk) + '/vacancy/' + str(new_vacancy.pk) + '/'
+
+        response = self.client.patch(url, format='json')
+        body = json.loads(response.content) 
+
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
-- 
GitLab


From 3aeff43bb67e48db465b6b4e29f97620748023d6 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sat, 5 Oct 2019 13:12:27 +0700
Subject: [PATCH 07/12] add new endpoint & model attr to accept only one job
 and

---
 core/models/vacancies.py      |  1 +
 core/serializers/vacancies.py |  2 +-
 core/views/vacancies.py       | 20 ++++++++++++++++++++
 kape/urls.py                  |  3 ++-
 4 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/core/models/vacancies.py b/core/models/vacancies.py
index f2b5dc49..8968e626 100644
--- a/core/models/vacancies.py
+++ b/core/models/vacancies.py
@@ -23,6 +23,7 @@ class Application(models.Model):
     BOOKMARKED = 2
     REJECTED = 3
     ACCEPTED = 4
+    ABORTED = 5
 
     cover_letter = models.TextField(null=True, blank=True)
     student = models.ForeignKey(Student, on_delete=models.CASCADE)
diff --git a/core/serializers/vacancies.py b/core/serializers/vacancies.py
index 313f2064..a2bd2e4f 100644
--- a/core/serializers/vacancies.py
+++ b/core/serializers/vacancies.py
@@ -61,7 +61,7 @@ class ApplicationStatusSerializer(serializers.ModelSerializer):
 
 class SupervisorStudentApplicationSerializer(serializers.ModelSerializer):
     def to_representation(self, instance):
-        status_map = ["new", "read", "bookmarked", "rejected", "accepted" ]
+        status_map = ["new", "read", "bookmarked", "rejected", "accepted","aborted" ]
         return {
             'name' : instance.student.full_name,
             'npm' : instance.student.npm,
diff --git a/core/views/vacancies.py b/core/views/vacancies.py
index 56165caa..2f805138 100644
--- a/core/views/vacancies.py
+++ b/core/views/vacancies.py
@@ -350,3 +350,23 @@ class BookmarkedVacancyByStudentViewSet(viewsets.GenericViewSet):
         student.bookmarked_vacancies.remove(vacancy)
         return Response(
             self.serializer_class(student.bookmarked_vacancies, many=True, context={'request': request}).data)
+
+class AcceptOfferByStudentViewSet(MultiSerializerViewSetMixin, viewsets.GenericViewSet):
+    queryset = Application.objects.all()
+    permission_classes = [IsAdminOrStudent]
+
+    def partial_update(self, request, student_id, pk=None):
+        """
+        Get list of a student {student_id}'s cancel offered vacancies
+        ---
+        """
+        student = get_object_or_404(Student.objects.all().order_by('-updated'), pk=student_id)
+        apps = Application.objects.filter(student=student)
+        
+        for a in apps:
+            if a.vacancy_id != int(pk):
+                serializer = ApplicationStatusSerializer(a, data={'status': 5}, partial=True)
+                if serializer.is_valid():
+                    serializer.save()
+        
+        return Response(SupervisorStudentApplicationSerializer(apps, many=True, context={'request': request}).data)
\ No newline at end of file
diff --git a/kape/urls.py b/kape/urls.py
index 5ae9c078..ade154c6 100755
--- a/kape/urls.py
+++ b/kape/urls.py
@@ -25,7 +25,7 @@ from core import apps
 from core.views.accounts import StudentViewSet, CompanyViewSet, SupervisorViewSet, UserViewSet, LoginViewSet, \
     CompanyRegisterViewSet
 from core.views.vacancies import VacancyViewSet, BookmarkedVacancyByStudentViewSet, StudentApplicationViewSet, \
-    CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet
+    CompanyApplicationViewSet, CompanyVacanciesViewSet, ApplicationViewSet, AcceptOfferByStudentViewSet
 from core.views.feedbacks import FeedbackViewSet
 
 schema_view = get_swagger_view()
@@ -40,6 +40,7 @@ router.register(r'vacancies', VacancyViewSet)
 router.register(r'applications', ApplicationViewSet)
 router.register(r'feedbacks', FeedbackViewSet)
 # router.register(r'students/(?P<student_id>\d+)/profile', StudentProfileViewSet)
+router.register(r'acceptoffer/(?P<student_id>\d+)/vacancy', AcceptOfferByStudentViewSet)
 router.register(r'students/(?P<student_id>\d+)/bookmarked-vacancies', BookmarkedVacancyByStudentViewSet,
                 base_name='bookmarked-vacancy-list')
 router.register(r'students/(?P<student_id>\d+)/applied-vacancies', StudentApplicationViewSet,
-- 
GitLab


From f129948728d7208bf3f4ef337cc8c93e8c8dfd50 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sun, 6 Oct 2019 00:32:37 +0700
Subject: [PATCH 08/12] merge

---
 core/migrations/0015_merge_20191005_2038.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 core/migrations/0015_merge_20191005_2038.py

diff --git a/core/migrations/0015_merge_20191005_2038.py b/core/migrations/0015_merge_20191005_2038.py
new file mode 100644
index 00000000..d4afc1b8
--- /dev/null
+++ b/core/migrations/0015_merge_20191005_2038.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-05 13:38
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0014_feedback'),
+        ('core', '0014_company_category'),
+    ]
+
+    operations = [
+    ]
-- 
GitLab


From 262d51ace3fc12219fbfc13681b5ce10a0b357c2 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sun, 6 Oct 2019 00:49:37 +0700
Subject: [PATCH 09/12] merge

---
 core/migrations/0017_merge_20191006_0049.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 core/migrations/0017_merge_20191006_0049.py

diff --git a/core/migrations/0017_merge_20191006_0049.py b/core/migrations/0017_merge_20191006_0049.py
new file mode 100644
index 00000000..a492afcb
--- /dev/null
+++ b/core/migrations/0017_merge_20191006_0049.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-05 17:49
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0015_merge_20191005_2038'),
+        ('core', '0016_merge_20191005_2235'),
+    ]
+
+    operations = [
+    ]
-- 
GitLab


From b684ce4ea19bb192f6d1aa67c435816fd09219de Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sun, 6 Oct 2019 01:16:03 +0700
Subject: [PATCH 10/12] delete

---
 core/migrations/0015_merge_20191005_2038.py | 16 ----------------
 1 file changed, 16 deletions(-)
 delete mode 100644 core/migrations/0015_merge_20191005_2038.py

diff --git a/core/migrations/0015_merge_20191005_2038.py b/core/migrations/0015_merge_20191005_2038.py
deleted file mode 100644
index d4afc1b8..00000000
--- a/core/migrations/0015_merge_20191005_2038.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.10.5 on 2019-10-05 13:38
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('core', '0014_feedback'),
-        ('core', '0014_company_category'),
-    ]
-
-    operations = [
-    ]
-- 
GitLab


From ebc020d1079d3c216dfaaeaaa249ab5f804b19d1 Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sun, 6 Oct 2019 01:19:24 +0700
Subject: [PATCH 11/12] delete

---
 core/migrations/0017_merge_20191006_0049.py | 16 ----------------
 1 file changed, 16 deletions(-)
 delete mode 100644 core/migrations/0017_merge_20191006_0049.py

diff --git a/core/migrations/0017_merge_20191006_0049.py b/core/migrations/0017_merge_20191006_0049.py
deleted file mode 100644
index a492afcb..00000000
--- a/core/migrations/0017_merge_20191006_0049.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.10.5 on 2019-10-05 17:49
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('core', '0015_merge_20191005_2038'),
-        ('core', '0016_merge_20191005_2235'),
-    ]
-
-    operations = [
-    ]
-- 
GitLab


From 377c2042fa0bb50fb422cb071f75ec3a5d6e359c Mon Sep 17 00:00:00 2001
From: atthoriq <at.thoriq@outlook.com>
Date: Sun, 6 Oct 2019 01:35:22 +0700
Subject: [PATCH 12/12] merge migration

---
 core/migrations/0017_merge_20191006_0134.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 core/migrations/0017_merge_20191006_0134.py

diff --git a/core/migrations/0017_merge_20191006_0134.py b/core/migrations/0017_merge_20191006_0134.py
new file mode 100644
index 00000000..d41f21a8
--- /dev/null
+++ b/core/migrations/0017_merge_20191006_0134.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-05 18:34
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0015_merge_20191005_2038'),
+        ('core', '0016_merge_20191005_2235'),
+    ]
+
+    operations = [
+    ]
-- 
GitLab