Fakultas Ilmu Komputer UI

vacancies.py 24.8 KB
Newer Older
1
from django.utils import timezone
2
from django.db.models import Q
3
from rest_framework import viewsets, status
4
from rest_framework.decorators import detail_route
5
from rest_framework.exceptions import ValidationError
6
from rest_framework.generics import get_object_or_404
Zamil Majdy's avatar
Zamil Majdy committed
7
from rest_framework.pagination import PageNumberPagination
8
from rest_framework.permissions import IsAuthenticated
9
from rest_framework.response import Response
Zamil Majdy's avatar
Zamil Majdy committed
10

11
from core.lib.mixins import MultiSerializerViewSetMixin
Zamil Majdy's avatar
Zamil Majdy committed
12
from core.lib.permissions import IsAdminOrStudent, IsAdminOrCompany, IsAdminOrVacancyOwner, AsAdminOrSupervisor, \
13
    VacancyApprovalPermission, IsAdminOrVacancyOwnerOrAuthenticatedReadOnly
14
from core.models import Student, Company
15
16
17
from core.models.vacancies import Vacancy, Application, ReasonRejected
from core.serializers.vacancies import VacancySerializer, ApplicationSerializer, ApplicationStatusSerializer,\
    VacancyVerifiedSerializer, SupervisorStudentApplicationSerializer, \
18
    VacancyMilestoneSerializer
Zamil Majdy's avatar
Zamil Majdy committed
19
from core.views.accounts import StudentViewSet
20

21
from datetime import datetime, time
22
from rest_framework import serializers
23

24
25
26
27
28
29
30
31
32
33
34
35

def date_validator(open_time, close_time):
    if open_time < str(datetime.today()):
        return {'is_valid': False, 'error': 'Waktu buka lowongan harus lebih dari hari ini!', 'status': status.HTTP_400_BAD_REQUEST}
    elif close_time < open_time:
        return {'is_valid': False, 'error': 'Waktu tutup lowongan harus lebih dari waktu buka lowongan!', 'status': status.HTTP_400_BAD_REQUEST}
    elif close_time == open_time:
        raise ValidationError('Waktu tutup dan buka lowongan tidak boleh sama!')
    else:
        return {'is_valid': True, 'error':'', 'status': status.HTTP_200_OK}


36
class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet):
37
    queryset = Vacancy.objects.all()
38
    serializer_class = VacancySerializer
39
40
41
    # serializer_action_classes = {
    #     'create': PostVacancySerializer
    # }
42
    permission_classes = [IsAdminOrCompany]
43
    pagination_class = PageNumberPagination
44

45
    def get_permissions(self):
46
        if self.action in ["retrieve", "list"]:
47
48
49
            return [IsAuthenticated()]
        return super(VacancyViewSet, self).get_permissions()

50
    def list(self, request, *args, **kwargs):
51
        verified = request.query_params['verified'] if 'verified' in request.query_params else "True"
52
53
54
55
56
        search = request.query_params['search'] if 'search' in request.query_params else None
        if search is not None:
            vacancies = Vacancy.objects.filter(Q(name__icontains=search) | Q(company__user__username__icontains=search))
        else:
            vacancies = Vacancy.objects.all()
57
        companies = [int(x) for x in request.query_params.getlist('company', [])]
58
        if verified.lower() in ("yes", "true", "t", "1"):
59
            vacancies = vacancies.filter(verified=True)
60
        if verified.lower() in {"no", "false", "f", "0"}:
Zamil Majdy's avatar
Zamil Majdy committed
61
            vacancies = vacancies.filter(verified=False)
62
63
64
65
66
67
68

        opened_only = str(request.query_params['opened_only']) \
            if 'opened_only' in request.query_params else "False"
        if opened_only.lower() in ("yes", "true", "t", "1"):
            today = datetime.combine(datetime.now().date(), time())
            vacancies = vacancies.filter(close_time__gte=today)

69
70
        if len(companies) > 0:
            vacancies = vacancies.filter(company__id__in=companies)
71

72
        page = self.paginate_queryset(vacancies)
root's avatar
root committed
73
74
        print(vacancies)
        print(request)
75
76
        if page is not None:
            return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)
77
        vacancies = Vacancy.objects.all()
78
79
        return Response(VacancySerializer(vacancies, many=True, context={'request': request}).data)

80
81
82
83
84
85
    def name_position_validator(self, names):
        for name in names.split(" "):
            if not name.isalpha():
                raise serializers.ValidationError("Name must alphabets only")
        return name

86
87
88
89
90
91
92
93
    def salary_validator(self, salary):
        if not isinstance(salary, int):
            raise serializers.ValidationError("Salary must number only")

    def amount_validator(self, amount):
        if not isinstance(amount, int):
            raise serializers.ValidationError("amount must number only")

94
95
    def create(self, request):
        data = request.data
RANI LASMA ULI's avatar
RANI LASMA ULI committed
96
        print("[LOG] data: "+str(data))
97
98
99
100
101
102
103
        company_set = Company.objects.filter(id=data['company'])
        if len(company_set) == 0:
            raise ValidationError('no company')
        company = company_set[0]
        open_time = data['open_time']
        close_time = data['close_time']
        name = data['name']
104
        self.name_position_validator(name)
105
        salary = data.get('salary') or 0
106
        self.salary_validator(salary)
107
        description = data['description']
108
        working_period = data['working_period']
Hadlina Rahmadinni's avatar
Hadlina Rahmadinni committed
109
        requirements = data['requirements']
RANI LASMA ULI's avatar
RANI LASMA ULI committed
110
111
112
113
114
        tag = ''
        try:
            tag = data['tag']
        except:
            print("Tag leaved in blank")
115

RANI LASMA ULI's avatar
RANI LASMA ULI committed
116
117
        # if close_time < open_time:
        #     raise ValidationError('Waktu tutup lowongan harus lebih dari waktu buka lowongan!')
118
        max_accepted_applicants = data['max_accepted_applicants']
119
120
121
        date_validity = date_validator(open_time, close_time)
        if not date_validity['is_valid']:
            return Response({'error': date_validity['error']}, status=date_validity['status'])
122
        vacancy = Vacancy(company=company, open_time=open_time, close_time=close_time, name=name, description=description, salary=salary, working_period=working_period, tag=tag, max_accepted_applicants=max_accepted_applicants)
Farah Alhaniy Efendi's avatar
Farah Alhaniy Efendi committed
123
124
        if 'benefits' in data:
            vacancy.benefits = data['benefits']
Refo Ilmiya Akbar's avatar
Refo Ilmiya Akbar committed
125
126
127
        if 'amount' in data:
            if isinstance(data['amount'], int):
                vacancy.amount = data['amount']
128
                self.amount_validator(data['amount'])
Refo Ilmiya Akbar's avatar
Refo Ilmiya Akbar committed
129
130
            else:
                return Response(status=status.HTTP_400_BAD_REQUEST)
131
132
133
134
135
        if 'responsibilities' in data:
            if isinstance(data['responsibilities'], basestring):
                vacancy.responsibilities = data['responsibilities']
            else:
                return Response(status=status.HTTP_400_BAD_REQUEST)
136
137
138
139
140
141
142
        vacancy.save()
        return Response(status=status.HTTP_200_OK)

    def partial_update(self, request, pk):
        data = request.data
        vacancy = Vacancy.objects.get(pk=pk)
        open_time = data['open_time']
143
        salary = data.get('salary') or 0
144
145
146
        close_time = data['close_time']
        name = data['name']
        description = data['description']
147
        working_period = data['working_period']
Hadlina Rahmadinni's avatar
Hadlina Rahmadinni committed
148
        requirements = data['requirements']
149
        max_accepted_applicants = data['max_accepted_applicants']
150
151
152
        date_validity = date_validator(open_time, close_time)
        if not date_validity['is_valid']:
            return Response({'error': date_validity['error']}, status=date_validity['status'])
153
154
155
        vacancy.open_time = open_time
        vacancy.close_time = close_time
        vacancy.name = name
156
        vacancy.salary = salary
157
        vacancy.description = description
158
        vacancy.max_accepted_applicants = max_accepted_applicants
159
        vacancy.working_period = working_period
160
        vacancy.responsibilities = data.get('responsibilities')
Farah Alhaniy Efendi's avatar
Farah Alhaniy Efendi committed
161
162
        if 'benefits' in data:
            vacancy.benefits = data['benefits']
Hadlina Rahmadinni's avatar
Hadlina Rahmadinni committed
163
        vacancy.requirements = requirements
Refo Ilmiya Akbar's avatar
Refo Ilmiya Akbar committed
164
165
166
167
168
        if 'amount' in data:
            if isinstance(data['amount'], int):
                vacancy.amount = data['amount']
            else:
                return Response(status=status.HTTP_400_BAD_REQUEST)
169
170
171
        vacancy.save()
        return Response(status=status.HTTP_200_OK)

172
173
174
175
176
177
178
    @detail_route(permission_classes=[IsAdminOrCompany])
    def count(self, request, pk=None):
        vacancy = self.get_object()
        count = Application.objects.filter(vacancy=vacancy).count()
        count_new = Application.objects.filter(vacancy=vacancy, status=Application.NEW).count()
        return Response({"count": count, "count_new": count_new}, status=status.HTTP_200_OK)

Zamil Majdy's avatar
Zamil Majdy committed
179
180
    @detail_route(methods=['patch'], permission_classes=[VacancyApprovalPermission],
                  serializer_class=VacancyVerifiedSerializer)
181
182
183
184
185
186
    def verify(self, request, pk=None):
        vacancy = self.get_object()
        serializer = self.get_serializer_class()(vacancy, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
Zamil Majdy's avatar
Zamil Majdy committed
187
        return Response({"error": "bad request"}, status=status.HTTP_400_BAD_REQUEST)
188

189

Zamil Majdy's avatar
Zamil Majdy committed
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class ApplicationViewSet(MultiSerializerViewSetMixin, viewsets.GenericViewSet):
    queryset = Application.objects.all()
    serializer_class = ApplicationSerializer
    serializer_action_classes = {
        'update': ApplicationStatusSerializer,
        'partial_update': ApplicationStatusSerializer,
        'list': SupervisorStudentApplicationSerializer
    }
    permission_classes = [IsAdminOrVacancyOwner]
    pagination_class = PageNumberPagination

    def list(self, request):
        applications = Application.objects.order_by('student')
        page = self.paginate_queryset(applications)
        serializer = self.get_serializer_class()
        if page is not None:
            return self.get_paginated_response(
                serializer(applications, many=True, context={'request': request}).data)
        return Response(serializer(applications, many=True, context={'request': request}).data)

210
211
212
213
214
    @detail_route(methods=['get'], permission_classes=[IsAdminOrStudent])
    def count(self, request, pk=None):
        count = Application.objects.filter(vacancy_id=pk).count()
        return Response({"count": count}, status=status.HTTP_200_OK)

Zamil Majdy's avatar
Zamil Majdy committed
215
216
217
218
219
    def partial_update(self, request, pk=None):
        application = self.get_object()
        serializer = self.get_serializer_class()(application, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
220
221
            if request.data['status'] == 3:
                ReasonRejected.objects.create(application=application, reason=request.data['reason'])
Zamil Majdy's avatar
Zamil Majdy committed
222
223
224
225
226
227
228
            return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    @detail_route(methods=['get'], permission_classes=[IsAdminOrVacancyOwner])
    def transcript(self, request, pk):
        """
Zamil Majdy's avatar
Zamil Majdy committed
229
        Get student's academic transcript on application {application_id}
Zamil Majdy's avatar
Zamil Majdy committed
230
231
232
        ---
        """
        application = self.get_object()
Zamil Majdy's avatar
Zamil Majdy committed
233
        return StudentViewSet.transcript(StudentViewSet(), request, application.student.pk)
Zamil Majdy's avatar
Zamil Majdy committed
234
235
236
237
238
239
240
241

    def get_permissions(self):
        if self.action == "list":
            return [AsAdminOrSupervisor()]
        return super(ApplicationViewSet, self).get_permissions()


class StudentApplicationViewSet(viewsets.GenericViewSet):
242
    serializer_class = ApplicationSerializer
243
    permission_classes = [IsAdminOrStudent]
244
    pagination_class = PageNumberPagination
245

246
    def list(self, request, student_id):
247
        """
248
        Get list of a student {student_id}'s applied vacancies
249
250
        ---
        """
251
        student = get_object_or_404(Student.objects.all(), pk=student_id)
252
        vacancy_ids = Application.objects.filter(student=student).values('vacancy')
253
254
255
256
257
        search = request.query_params['search'] if 'search' in request.query_params else None
        if search is not None:
            vacancies = Vacancy.objects.filter(Q(id__in=vacancy_ids) & (Q(name__icontains=search) | Q(company__user__username__icontains=search)))
        else:
            vacancies = Vacancy.objects.filter(id__in=vacancy_ids)
258
259
260
261
        page = self.paginate_queryset(vacancies)
        if page is not None:
            return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)
        return Response(VacancySerializer(vacancies, many=True, context={'request': request}).data)
262
263

    def create(self, request, student_id):
264
265
266
267
268
        """
        Create a new application for student {student_id}
        ---
        parameters:
            - name: body
269
              description: JSON object containing an integer 'vacancy_id' and a string 'cover_letter'
270
271
272
273
              required: true
              type: string
              paramType: body
        """
274
275
        cover_letter = request.data.get('cover_letter')
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=request.data.get('vacancy_id'))
276
277
278
        if vacancy.close_time < timezone.now():
            return Response({"error": "vacancy is closed"}, \
                                status=status.HTTP_400_BAD_REQUEST)
279
        student = get_object_or_404(Student.objects.all(), pk=student_id)
280
281
        if Application.objects.filter(vacancy=vacancy, student=student).exists():
            raise ValidationError("You have already applied for the vacancy")
282
283
284
        application = Application(vacancy=vacancy, student=student, cover_letter=cover_letter)
        application.save()
        return Response(ApplicationSerializer(application, context={'request': request}).data)
285
286

    def destroy(self, request, student_id, pk):
287
288
289
290
        """
        Remove a application {id} for student {student_id}
        ---
        """
291
292
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=pk)
        student = get_object_or_404(Student.objects.all(), pk=student_id)
293
        application = get_object_or_404(Application.objects.all(), student=student, vacancy=vacancy)
294
        application.delete()
295
        return Response(ApplicationSerializer(application, context={'request': request}).data)
296

297

298
299
300
class StatusError(Exception):
    pass

301

302
303
class UnauthorizeError(Exception):
    pass
304

305

306
307
class CompanyApplicationViewSet(viewsets.GenericViewSet):
    queryset = Application.objects.all()
308
    permission_classes = [IsAdminOrCompany]
309
    pagination_class = PageNumberPagination
310
311
312
313
314
315

    def list(self, request, company_id):
        """
        Get list of company {company_id}'s applications
        ---
        """
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
        try:
            company = self.__get_company_list_by_company_id(request, company_id)
            vacancies = self.__get_vacancy_list_filter_by_company(company)
            applications = self.__get_application_list_filter_by_vacancies(vacancies)
            st = self.__get_status_from_request_param(request)
            if st is not None:
                st = int(st)
                self.__validating_application_status(st)
                applications = applications.filter(status=st)
            page = self.paginate_queryset(applications)
            if page is not None:
                return self.get_paginated_response(
                    ApplicationSerializer(page, many=True, context={'request': request}).data)
            return Response(ApplicationSerializer(applications, many=True, context={'request': request}).data)
        except UnauthorizeError:
331
            return Response({"error": "forbidden"}, status=status.HTTP_403_FORBIDDEN)
332
333
        except (StatusError, ValueError):
            return Response({"error": "status must be an integer between 0 and 4"}, \
334
                            status=status.HTTP_400_BAD_REQUEST)
335

336
337
    @detail_route(methods=["get"])
    def by_vacancy(self, request, company_id, pk=None):
Zamil Majdy's avatar
Zamil Majdy committed
338
339
340
341
        """
        Get list of company {company_id}'s applications by vacancy {id}
        ---
        """
342
343
344
345
346
347
348
349
        try:
            if pk is None:
                return list(self, request, company_id)
            company = self.__get_company_list_by_company_id(request, company_id)
            vacancy = self.__get_vacancy_list_by_pk(pk, company)
            applications = self.__get_aplication_by_vacancy(vacancy)
            st = self.__get_status_from_request_param(request)
            if st is not None:
350
                st = int(st)
351
                self.__validating_application_status(st)
352
                applications = applications.filter(status=st)
353
354
355
356
357
358
359
360
361
            page = self.paginate_queryset(applications)
            if page is not None:
                return self.get_paginated_response(
                    ApplicationSerializer(page, many=True, context={'request': request}).data)
            return Response(ApplicationSerializer(applications, many=True, context={'request': request}).data)
        except UnauthorizeError:
            return Response({"error": "forbidden"}, status=status.HTTP_403_FORBIDDEN)
        except (StatusError, ValueError):
            return Response({"error": "status must be an integer between 0 and 4"}, \
362
                            status=status.HTTP_400_BAD_REQUEST)
363
364
365
366
367

    def __get_company_list_by_company_id(self, request, company_id):
        company = get_object_or_404(Company.objects.all().order_by('-updated'), pk=company_id)
        if not self.__validating_user(request, company):
            raise UnauthorizeError
Refo Ilmiya Akbar's avatar
Refo Ilmiya Akbar committed
368
        return company
369
370
371

    def __validating_user(self, request, company):
        return request.user.is_superuser or request.user == company.user
Refo Ilmiya Akbar's avatar
Refo Ilmiya Akbar committed
372

373
    def __get_vacancy_list_by_pk(self, pk, company):
374
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=pk)
375
376
377
378
379
380
381
382
383
        if not self.__validating_vacancy(vacancy, company):
            raise UnauthorizeError
        return vacancy

    def __validating_vacancy(self, vacancy, company):
        return vacancy.company == company

    def __get_vacancy_list_filter_by_company(self, company):
        return Vacancy.objects.filter(company=company)
384

385
386
387
388
389
    def __get_application_list_filter_by_vacancies(self, vacancies):
        return Application.objects.filter(vacancy__in=vacancies)

    def __get_aplication_by_vacancy(self, vacancy):
        return Application.objects.filter(vacancy=vacancy)
390

391
392
    def __get_status_from_request_param(self, request):
        return request.query_params.get('status', None)
Refo Ilmiya Akbar's avatar
Refo Ilmiya Akbar committed
393

394
    def __validating_application_status(self, status):
Syahrul Findi's avatar
Syahrul Findi committed
395
        list_status = {0 : "NEW", 1 : "READ", 2 : "BOOKMARKED", 3 : "REJECTED", 4: "ACCEPTED", 6: "FINISHED"}
396
397
398
        if status not in list_status:
            raise StatusError
        return True
399

400

401
class CompanyVacanciesViewSet(viewsets.GenericViewSet):
402
    queryset = Vacancy.objects.all()
403
    pagination_class = PageNumberPagination
404
    permission_classes = [IsAdminOrCompany]
405
406
407
408
409
410

    def list(self, request, company_id):
        """
        Get list of company {company_id}'s vacancies
        ---
        """
411
        company = get_object_or_404(Company.objects.all().order_by('-updated'), pk=company_id)
412
413
        if not request.user.is_superuser and request.user != company.user:
            return Response({"error": "forbidden"}, status=status.HTTP_403_FORBIDDEN)
414
        vacancies = Vacancy.objects.filter(company=company)
415
416
417
        page = self.paginate_queryset(vacancies)
        if page is not None:
            return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)
418
419
420
        return Response(VacancySerializer(vacancies, many=True, context={'request': request}).data)


421
422
423
424
425
class BookmarkedVacancyByStudentViewSet(viewsets.GenericViewSet):
    serializer_class = VacancySerializer
    permission_classes = [IsAdminOrStudent]

    def list(self, request, student_id):
426
427
428
429
        """
        Get list of a student {student_id}'s bookmarked vacancies
        ---
        """
430
        student = get_object_or_404(Student.objects.all(), pk=student_id)
431
432
433
434
435
        search = request.query_params['search'] if 'search' in request.query_params else None
        if search is not None:
            vacancies = student.bookmarked_vacancies.filter(Q(name__icontains=search) | Q(company__user__username__icontains=search))
        else:
            vacancies = student.bookmarked_vacancies.all()
436
437
438
439
        page = self.paginate_queryset(vacancies)
        if page is not None:
            return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)
        return Response(VacancySerializer(vacancies, many=True, context={'request': request}).data)
440
441

    def create(self, request, student_id):
442
443
444
445
446
447
448
449
450
451
        """
        Bookmarks a vacancy for student {student_id}
        ---
        parameters:
            - name: body
              description: JSON object containing only one string: vacancy_id
              required: true
              type: string
              paramType: body
        """
452
453
454
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=request.data['vacancy_id'])
        student = get_object_or_404(Student.objects.all(), pk=student_id)
        student.bookmarked_vacancies.add(vacancy)
Zamil Majdy's avatar
Zamil Majdy committed
455
456
        return Response(
            self.serializer_class(student.bookmarked_vacancies, many=True, context={'request': request}).data)
457
458

    def destroy(self, request, student_id, pk):
459
460
461
462
        """
        Remove bookmark {id} for student {student_id}
        ---
        """
463
464
465
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=pk)
        student = get_object_or_404(Student.objects.all(), pk=student_id)
        student.bookmarked_vacancies.remove(vacancy)
Zamil Majdy's avatar
Zamil Majdy committed
466
467
        return Response(
            self.serializer_class(student.bookmarked_vacancies, many=True, context={'request': request}).data)
468

469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539

class VacancyMilestoneViewSet(viewsets.GenericViewSet):
    serializer_class = VacancyMilestoneSerializer
    permission_classes = [IsAdminOrVacancyOwnerOrAuthenticatedReadOnly]

    def list(self, request, vacancy_id):
        """
        Get list of a vacancy {vacancy_id}'s milestone plans
        ---
        """
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=vacancy_id)
        milestones = vacancy.milestones.all()
        page = self.paginate_queryset(milestones)
        if page is not None:
            return self.get_paginated_response(
               self.serializer_class(page, many=True, context={'request': request}).data)
        return Response(self.serializer_class(milestones, many=True, context={'request': request}).data)

    def create(self, request, vacancy_id):
        """
        Create a new milestone for vacancy {vacancy_id}
        ---
        parameters:
            - name: body
              description: JSON object containing 'name' string, 'detail' string, 'expected_start' date string, and
                           'expected_finish' date string
              required: true
              type: string
              paramType: body
        """
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=vacancy_id)
        milestone_serializer = self.serializer_class(data=request.data)
        if milestone_serializer.is_valid():
            milestone = milestone_serializer.save(vacancy=vacancy)
            return Response(self.serializer_class(milestone, context={'request': request}).data,
                            status=status.HTTP_200_OK)
        return Response(milestone_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def update(self, request, vacancy_id, pk):
        """
        Create existing milestone {pk} for vacancy {vacancy_id}
        ---
        parameters:
            - name: body
              description: JSON object containing 'name' string, 'detail' string, 'expected_start' date string, and
                           'expected_finish' date string
              required: true
              type: string
              paramType: body
        """
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=vacancy_id)
        old_milestone = get_object_or_404(vacancy.milestones.all(), pk=pk)
        milestone_serializer = self.serializer_class(old_milestone, data=request.data)
        if milestone_serializer.is_valid():
            milestone = milestone_serializer.save(vacancy=vacancy)
            return Response(self.serializer_class(milestone, context={'request': request}).data,
                            status=status.HTTP_200_OK)
        return Response(milestone_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def destroy(self, request, vacancy_id, pk):
        """
        Remove existing milestone {pk} from vacancy {vacancy_id}
        ---
        """
        vacancy = get_object_or_404(Vacancy.objects.all(), pk=vacancy_id)
        milestone = get_object_or_404(vacancy.milestones.all(), pk=pk)
        milestone.delete()
        return Response(
            self.serializer_class(vacancy.milestones.all(), many=True, context={'request': request}).data)


540
541
542
543
544
545
546
547
548
549
550
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)
551

552
553
554
555
556
        for a in apps:
            if a.vacancy_id != int(pk):
                serializer = ApplicationStatusSerializer(a, data={'status': 5}, partial=True)
                if serializer.is_valid():
                    serializer.save()
557
558

        return Response(SupervisorStudentApplicationSerializer(apps, many=True, context={'request': request}).data)