Fakultas Ilmu Komputer UI

Commit 49d4cc0d authored by Ichlasul Affan's avatar Ichlasul Affan
Browse files

Merge branch '1606895606-90' into 'master'

Upgrade Python 2 and Django 1.11 to Python 3 and Django 2.2 (#90)

Closes #90

See merge request !221
parents 1959a89c 38030300
Pipeline #26599 passed with stages
in 18 minutes and 38 seconds
......@@ -327,4 +327,7 @@ test/*
Dockerfile
docker-compose.yml
LICENSE
README.md
\ No newline at end of file
README.md
kape/files/**/*
files/**/*
......@@ -327,3 +327,4 @@ package-lock.json
.DS_Store
kape/files/**/*
files/**/*
---
image: python:2-stretch
image: python:buster
stages:
- build
......@@ -27,9 +27,9 @@ test-backend:
stage: test
script:
- pip install -r requirements.txt
- python manage.py makemigrations
- python manage.py migrate
- python manage.py test
- python3 manage.py makemigrations
- python3 manage.py migrate
- python3 manage.py test
artifacts:
paths:
- test/
......@@ -60,7 +60,7 @@ pages:
expire_in: 30 days
staging:
type: deploy
stage: deploy
script:
- apt-get update -qy
- apt-get install sshpass
......@@ -76,7 +76,7 @@ staging:
- develop
production:
type: deploy
stage: deploy
script:
- apt-get update -qy
- apt-get install sshpass
......
......@@ -7,7 +7,7 @@ RUN npm config set proxy ${NPM_PROXY} \
&& npm install \
&& npm run build-production
FROM python:2-stretch AS app
FROM python:buster AS app
# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
......@@ -35,7 +35,7 @@ COPY --chown=${USERNAME} --from=frontend-builder /home/kape/app/assets/bundles .
COPY --chown=${USERNAME} --from=frontend-builder /home/kape/app/webpack-stats.json ./
RUN mkdir -p /home/kape/assets \
&& mkdir -p /home/kape/files \
&& python manage.py collectstatic --noinput
&& python3 manage.py collectstatic --noinput
# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=
......@@ -81,4 +81,4 @@ LABEL org.label-schema.build-date=${IMAGE_CREATED} \
org.label-schema.version=${IMAGE_VERSION} \
org.label-schema.vendor=${IMAGE_VENDOR} \
org.label-schema.title=${IMAGE_TITLE} \
org.label-schema.description=${IMAGE_DESCRIPTION}
\ No newline at end of file
org.label-schema.description=${IMAGE_DESCRIPTION}
......@@ -19,7 +19,7 @@
## Install
This project uses Python 2 and Node.js v8 for building the backend and frontend,
This project uses Python 3 and Node.js v8 for building the backend and frontend,
respectively. The backend uses Django Framework and PostgreSQL database, while
the frontend is developed using React. You need to install the required
dependencies prior to building and contributing to the project.
......@@ -34,7 +34,7 @@ dependencies prior to building and contributing to the project.
>
> Once `nvm` has been installed, install Node.js and activate it by executing
> `nvm install 10.16.0` followed by `nvm activate 10.16.0`.
- [Python 2.7.16](https://www.python.org/downloads/release/) and `pip` package
- [Python 3.6 or newer](https://www.python.org/downloads/release/) and `pip` package
manager.
> Note: We recommend using _virtual environment_ to isolate project-specific
> Python packages from system-level packages. You can install and use
......@@ -49,7 +49,7 @@ dependencies prior to building and contributing to the project.
> database. If you had to use locally-installed database, make sure your
> own data will not interleave with the data generated by this project.
Verify that Node.js and Python 2 have been successfully installed. Make sure
Verify that Node.js and Python 3 have been successfully installed. Make sure
the interpreter for both platforms can be invoked from the shell. For example,
in `bash` shell (macOS or GNU/Linux-based OS):
......@@ -57,13 +57,13 @@ in `bash` shell (macOS or GNU/Linux-based OS):
# Assuming there is a Python virtual environment directory named `env` in
# current path
$ source env/bin/activate
$ python --version # or: python2 --version
Python 2.7.16
$ python3 --version
Python 3.7.5
$ node --version
v10.16.0
```
Now install the packages required by Node.js and Python 2:
Now install the packages required by Node.js and Python 3:
```bash
npm install
......@@ -77,8 +77,8 @@ ownership of the new database to `kape` user. Once you have finished setting up
the database, perform database migration and seeding:
```bash
python manage.py migrate
python manage.py loaddata seeder.json
python3 manage.py migrate
python3 manage.py loaddata seeder.json
```
> Note: Can't connect to the database? Adjust the database connection settings
......@@ -88,7 +88,7 @@ Finally, verify that the test suites pass:
```bash
# Run the test suite for backend
python manage.py test
python3 manage.py test
```
To run the test suite for frontend, you need to build the frontend first before
......@@ -104,7 +104,7 @@ npm run karma
To run the API (backend) server:
```bash
python manage.py runserver
python3 manage.py runserver
```
To serve the frontend:
......@@ -126,8 +126,8 @@ Web server:
```bash
docker-compose up --build --detach
docker-compose run --rm app python manage.py migrate
docker-compose run --rm app python manage.py loaddata seeder.json
docker-compose run --rm app python3 manage.py migrate
docker-compose run --rm app python3 manage.py loaddata seeder.json
```
> Explanation:
......
......@@ -37,9 +37,9 @@ class Migration(migrations.Migration):
('status', models.IntegerField(default=0, validators=[django.core.validators.MaxValueValidator(2), django.core.validators.MinValueValidator(0)])),
('logo', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_company_logo_file_path, validators=[core.lib.validators.validate_image_file_extension])),
('address', models.CharField(blank=True, max_length=1000, null=True)),
('category', models.CharField(default=b'Belum ada kategori perusahaan', max_length=140)),
('category', models.CharField(default='Belum ada kategori perusahaan', max_length=140)),
('size', models.CharField(blank=True, default=0, max_length=10, null=True)),
('website', models.CharField(default=b'Belum ada link website', max_length=100)),
('website', models.CharField(default='Belum ada link website', max_length=100)),
('linkedin_url', models.URLField(blank=True, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
......@@ -52,7 +52,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('title', models.CharField(blank=True, default=b'', max_length=100)),
('title', models.CharField(blank=True, default='', max_length=100)),
('content', models.TextField()),
('companyId', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Company')),
],
......@@ -79,16 +79,16 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('npm', models.IntegerField(unique=True, validators=[core.lib.validators.validate_npm])),
('resume', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_resume_file_path, validators=[django.core.validators.FileExtensionValidator([b'pdf'])])),
('resume', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_resume_file_path, validators=[django.core.validators.FileExtensionValidator(['pdf'])])),
('sertifikat', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_sertifikat_file_path, validators=[core.lib.validators.validate_document_file_extension])),
('phone_number', models.CharField(blank=True, db_index=True, max_length=100, null=True, validators=[django.core.validators.RegexValidator(b'^0\\d{1,11}$')])),
('phone_number', models.CharField(blank=True, db_index=True, max_length=100, null=True, validators=[django.core.validators.RegexValidator('^0\\d{1,11}$')])),
('gender', models.CharField(blank=True, max_length=30, null=True)),
('birth_place', models.CharField(blank=True, max_length=30, null=True)),
('birth_date', models.DateField(blank=True, null=True)),
('major', models.CharField(blank=True, max_length=30, null=True)),
('batch', models.CharField(blank=True, max_length=4, null=True)),
('show_transcript', models.BooleanField(default=False)),
('photo', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_photo_file_path, validators=[django.core.validators.FileExtensionValidator([b'jpg', b'jpeg', b'png'])])),
('photo', models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_photo_file_path, validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg', 'png'])])),
('self_description', models.CharField(blank=True, db_index=True, max_length=500, null=True)),
('portfolio_link', models.URLField(blank=True, null=True)),
('linkedin_url', models.URLField(blank=True, null=True)),
......@@ -104,10 +104,10 @@ class Migration(migrations.Migration):
('github_url', models.URLField(blank=True, null=True)),
('gitlab_url', models.URLField(blank=True, null=True)),
('intro', models.CharField(blank=True, max_length=50, null=True)),
('expected_salary', models.CharField(blank=True, max_length=10, null=True, validators=[django.core.validators.RegexValidator(b'^\\d{0,10}$')])),
('expected_salary', models.CharField(blank=True, max_length=10, null=True, validators=[django.core.validators.RegexValidator('^\\d{0,10}$')])),
('job_seeking_status', models.CharField(blank=True, max_length=30, null=True)),
('student_gpa', models.FloatField(blank=True, db_column=b'student_gpa', default=1.0, null=True, validators=[core.lib.validators.validate_student_gpa])),
('student_toefl', models.IntegerField(blank=True, db_column=b'toefl', default=0, null=True)),
('student_gpa', models.FloatField(blank=True, db_column='student_gpa', default=1.0, null=True, validators=[core.lib.validators.validate_student_gpa])),
('student_toefl', models.IntegerField(blank=True, db_column='toefl', default=0, null=True)),
('volunteer', models.CharField(blank=True, max_length=100, null=True)),
('awards', models.CharField(blank=True, max_length=100, null=True)),
('projects', models.CharField(blank=True, max_length=100, null=True)),
......@@ -115,7 +115,7 @@ class Migration(migrations.Migration):
('languages', models.CharField(blank=True, max_length=100, null=True)),
('seminar', models.CharField(blank=True, max_length=100, null=True)),
('interests', models.CharField(blank=True, max_length=100, null=True)),
('dependants', models.IntegerField(blank=True, db_column=b'dependants', default=0, null=True)),
('dependants', models.IntegerField(blank=True, db_column='dependants', default=0, null=True)),
('related_course', models.CharField(blank=True, max_length=200, null=True)),
],
options={
......@@ -128,7 +128,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('nip', models.IntegerField(unique=True, validators=[django.core.validators.MinValueValidator(100000000), django.core.validators.MaxValueValidator(9999999999L)])),
('nip', models.IntegerField(unique=True, validators=[django.core.validators.MinValueValidator(100000000), django.core.validators.MaxValueValidator(9999999999)])),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
......@@ -155,7 +155,7 @@ class Migration(migrations.Migration):
('tag', models.TextField(blank=True)),
('salary', models.IntegerField(default=0)),
('recruiter_activity', models.CharField(blank=True, max_length=10, null=True)),
('office_address', models.TextField(blank=True, default=b'')),
('office_address', models.TextField(blank=True, default='')),
('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vacancies', to='core.Company')),
],
options={
......@@ -177,7 +177,7 @@ class Migration(migrations.Migration):
name='ReasonRejected',
fields=[
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='core.Application')),
('reason', models.TextField(default=b'Tidak memenuhi kualifikasi perusahaan.')),
('reason', models.TextField(default='Tidak memenuhi kualifikasi perusahaan.')),
],
),
migrations.AddField(
......
# Generated by Django 2.2.8 on 2019-12-04 04:51
import core.lib.validators
import core.models.accounts
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='company',
name='category',
field=models.CharField(default='Belum ada kategori perusahaan', max_length=140),
),
migrations.AlterField(
model_name='company',
name='website',
field=models.CharField(default='Belum ada link website', max_length=100),
),
migrations.AlterField(
model_name='feedback',
name='title',
field=models.CharField(blank=True, default='', max_length=100),
),
migrations.AlterField(
model_name='reasonrejected',
name='reason',
field=models.TextField(default='Tidak memenuhi kualifikasi perusahaan.'),
),
migrations.AlterField(
model_name='student',
name='dependants',
field=models.IntegerField(blank=True, db_column='dependants', default=0, null=True),
),
migrations.AlterField(
model_name='student',
name='expected_salary',
field=models.CharField(blank=True, max_length=10, null=True, validators=[django.core.validators.RegexValidator('^\\d{0,10}$')]),
),
migrations.AlterField(
model_name='student',
name='phone_number',
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, validators=[django.core.validators.RegexValidator('^0\\d{1,11}$')]),
),
migrations.AlterField(
model_name='student',
name='photo',
field=models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_photo_file_path, validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg', 'png'])]),
),
migrations.AlterField(
model_name='student',
name='resume',
field=models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_resume_file_path, validators=[django.core.validators.FileExtensionValidator(['pdf'])]),
),
migrations.AlterField(
model_name='student',
name='student_gpa',
field=models.FloatField(blank=True, db_column='student_gpa', default=1.0, null=True, validators=[core.lib.validators.validate_student_gpa]),
),
migrations.AlterField(
model_name='student',
name='student_toefl',
field=models.IntegerField(blank=True, db_column='toefl', default=0, null=True),
),
migrations.AlterField(
model_name='vacancy',
name='office_address',
field=models.TextField(blank=True, default=''),
),
]
......@@ -71,7 +71,7 @@ class Student(models.Model):
"""
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
user = models.OneToOneField(User)
user = models.OneToOneField(User, on_delete=models.CASCADE)
npm = models.IntegerField(validators=[validate_npm], unique=True)
resume = models.FileField(upload_to=get_student_resume_file_path, null=True, blank=True,
validators=[FileExtensionValidator(['pdf'])])
......@@ -151,7 +151,7 @@ class Company(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
user = models.OneToOneField(User)
user = models.OneToOneField(User, on_delete=models.CASCADE)
description = models.TextField()
status = models.IntegerField(default=NEW, validators=[MaxValueValidator(2), MinValueValidator(0)])
logo = models.FileField(upload_to=get_company_logo_file_path, null=True, blank=True,
......@@ -179,7 +179,7 @@ class Supervisor(models.Model):
"""
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
user = models.OneToOneField(User)
user = models.OneToOneField(User, on_delete=models.CASCADE)
nip = models.IntegerField(validators=[MinValueValidator(100000000), MaxValueValidator(9999999999)], unique=True)
@property
......
......@@ -5,7 +5,7 @@ from django.utils import timezone
class Vacancy(models.Model):
company = models.ForeignKey(Company, related_name="vacancies", null=False)
company = models.ForeignKey(Company, related_name="vacancies", null=False, on_delete=models.CASCADE)
verified = models.BooleanField(default=False)
open_time = models.DateTimeField()
description = models.TextField(blank=True)
......
......@@ -451,7 +451,7 @@ class ProfileUpdateTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def _create_test_file(self, path):
f = open(path, 'r')
f = open(path, 'rb')
return {'pdf_file': f}
def test_company_profile_update(self):
......
from rest_framework import viewsets, status
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response
......@@ -14,8 +14,8 @@ class CompanyViewSet(viewsets.ModelViewSet):
permission_classes = [IsAdminOrSelfOrReadOnly, IsAdminOrCompany]
filter_fields = ('status',)
@detail_route(methods=['patch'], permission_classes=[IsAdminOrCompany],
serializer_class=CompanyUpdateSerializer, parser_classes=(MultiPartParser, FormParser,))
@action(detail=True, methods=['patch'], permission_classes=[IsAdminOrCompany],
serializer_class=CompanyUpdateSerializer, parser_classes=(MultiPartParser, FormParser,))
def profile(self, request, pk=None):
"""
Update company's profile information
......
import re
from rest_framework import viewsets, status
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import get_object_or_404
from rest_framework.parsers import FormParser, MultiPartParser
......@@ -33,8 +33,8 @@ class StudentViewSet(viewsets.ModelViewSet):
return [IsAuthenticated(), IsAdminOrSupervisorOrCompanyOrSelf()]
return super(StudentViewSet, self).get_permissions()
@detail_route(methods=['patch'], permission_classes=[IsAdminOrStudent],
serializer_class=StudentUpdateSerializer, parser_classes=(MultiPartParser, FormParser,))
@action(detail=True, methods=['patch'], permission_classes=[IsAdminOrStudent],
serializer_class=StudentUpdateSerializer, parser_classes=(MultiPartParser, FormParser,))
def profile(self, request, pk=None):
"""
Update student {student_id}'s profile information
......@@ -95,7 +95,7 @@ class StudentViewSet(viewsets.ModelViewSet):
else:
return Response({}, status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
@detail_route(methods=['get'], permission_classes=[IsAdminOrStudent])
@action(detail=True, methods=['get'], permission_classes=[IsAdminOrStudent])
def transcript(self, request, pk):
"""
Get student {student_id}'s academic transcript
......
from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework.decorators import list_route
from rest_framework.decorators import action
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
......@@ -12,7 +12,7 @@ class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
permission_classes = [IsAdminUser]
@list_route(methods=['get'], permission_classes=[IsAuthenticated])
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
def me(self, request):
"""
Get current user's details
......
import requests
from django.conf import settings
import views_constants
from core.views import views_constants
def get_access_token(username, password):
......
......@@ -3,13 +3,13 @@ from datetime import datetime, time
from django.db.models import Q
from django.utils import timezone
from rest_framework import viewsets, status
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
import views_constants
from core.views import views_constants
from core.lib.mixins import MultiSerializerViewSetMixin
from core.lib.permissions import IsAdminOrStudent, IsAdminOrCompany, IsAdminOrVacancyOwner, AsAdminOrSupervisor, \
......@@ -130,17 +130,17 @@ class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet):
def office_address_validator(self, office_address):
print(office_address)
print(type(office_address))
if not isinstance(office_address, basestring):
if not isinstance(office_address, str):
print('DEBUG ERROR')
raise ValidationError(views_constants.ERROR_INVALID_OFFICE_ADDRESS)
def responsibilities_validator(self, responsibilities):
if not isinstance(responsibilities, basestring):
if not isinstance(responsibilities, str):
raise ValidationError(views_constants.ERROR_RESPONSIBILITIES_MUST_STRING)
def recruiter_activity_validator(self, recruiter_activity):
enum_recruiter_activity = ['Selalu', 'Sering', 'Kadang', 'Jarang', 'Tidak Pernah']
if (not isinstance(recruiter_activity, basestring)) or (recruiter_activity not in enum_recruiter_activity):
if (not isinstance(recruiter_activity, str)) or (recruiter_activity not in enum_recruiter_activity):
raise ValidationError(views_constants.ERROR_INVALID_RECRUITER_RESPONSE)
def date_range_validator(self, open_time, close_time):
......@@ -216,15 +216,15 @@ class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet):
return Response({views_constants.ERROR: str(e.detail[0])}, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_200_OK)
@detail_route(permission_classes=[IsAdminOrCompany])
@action(detail=True, 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({views_constants.COUNT: count, views_constants.COUNT_NEW: count_new}, status=status.HTTP_200_OK)
@detail_route(methods=[views_constants.METHOD_PATCH], permission_classes=[VacancyApprovalPermission],
serializer_class=VacancyVerifiedSerializer)
@action(detail=True, methods=[views_constants.METHOD_PATCH], permission_classes=[VacancyApprovalPermission],
serializer_class=VacancyVerifiedSerializer)
def verify(self, request, pk=None):
vacancy = self.get_object()
serializer = self.get_serializer_class()(vacancy, data=request.data, partial=True)
......@@ -254,7 +254,7 @@ class ApplicationViewSet(MultiSerializerViewSetMixin, viewsets.GenericViewSet):
serializer(applications, many=True, context={views_constants.REQUEST: request}).data)
return Response(serializer(applications, many=True, context={views_constants.REQUEST: request}).data)
@detail_route(methods=[views_constants.METHOD_GET], permission_classes=[IsAdminOrStudent])
@action(detail=True, methods=[views_constants.METHOD_GET], permission_classes=[IsAdminOrStudent])
def count(self, request, pk=None):
count = Application.objects.filter(vacancy_id=pk).count()
return Response({views_constants.COUNT: count}, status=status.HTTP_200_OK)
......@@ -273,7 +273,7 @@ class ApplicationViewSet(MultiSerializerViewSetMixin, viewsets.GenericViewSet):
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@detail_route(methods=[views_constants.METHOD_GET], permission_classes=[IsAdminOrVacancyOwner])
@action(detail=True, methods=[views_constants.METHOD_GET], permission_classes=[IsAdminOrVacancyOwner])
def transcript(self, request, pk):
"""
Get student's academic transcript on application {application_id}
......@@ -345,7 +345,7 @@ class StudentApplicationViewSet(viewsets.GenericViewSet):
application.delete()
return Response(ApplicationSerializer(application, context={views_constants.REQUEST: request}).data)
@detail_route(methods=[views_constants.METHOD_GET], permission_classes=[IsAdminOrStudent])
@action(detail=True, methods=[views_constants.METHOD_GET], permission_classes=[IsAdminOrStudent])
def reason(self, request, student_id, pk):
"""
Get the reason for rejection of application {id} for student {student_id}
......@@ -389,7 +389,7 @@ class CompanyApplicationViewSet(viewsets.GenericViewSet):
return Response({views_constants.ERROR: views_constants.ERROR_INVALID_STATUS_CODE},
status=status.HTTP_400_BAD_REQUEST)
@detail_route(methods=[views_constants.METHOD_GET])
@action(detail=True, methods=[views_constants.METHOD_GET])
def by_vacancy(self, request, company_id, pk=None):
"""
Get list of company {company_id}'s applications by vacancy {id}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment