diff --git a/assets/css/custom.css b/assets/css/custom.css
index 9b7cf50a1356d41530baa447984f2fb2abfd873a..5cba8ecf80180ee7ea93f79b3754f18e9afd0141 100755
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -289,4 +289,27 @@ card .formRegis{
.ui.segment.kop {
line-height: 5px;
+}
+
+.search-container {
+ width: fit-content;
+ margin: auto;
+}
+
+.search-form {
+ border: none;
+ border-bottom: solid #0d5aa7 2px;
+ text-decoration: none;
+ outline: none;
+ font-size: 14pt;
+}
+
+.search-button {
+ background-color: #0d5aa7;
+ color: white;
+ border: none;
+ padding: 10px;
+ font-size: 14pt;
+ border-radius: 5px;
+ margin-left: 10px;
}
\ No newline at end of file
diff --git a/assets/js/components/Pagination.jsx b/assets/js/components/Pagination.jsx
index d2a50ae2229e941d73ec3c5eda0a4c534f620ded..d278cb504272e2aaa86c59b7f2c7dec67d3a2de0 100644
--- a/assets/js/components/Pagination.jsx
+++ b/assets/js/components/Pagination.jsx
@@ -2,6 +2,7 @@ import React from 'react';
import { Menu, Container, Icon, Loader } from 'semantic-ui-react';
import Server from '../lib/Server';
import ModalAlert from '../components/ModalAlert';
+import Pane from "./Pane";
export default class Pagination extends React.Component {
@@ -29,38 +30,85 @@ export default class Pagination extends React.Component {
dir: 0,
start: true,
finish: false,
+ search: '',
};
this.handleNext = this.handleNext.bind(this);
this.handlePrev = this.handlePrev.bind(this);
this.getItemsData = this.getItemsData.bind(this);
this.handleMovement = this.handleMovement.bind(this);
+ this.handleSearchChange = this.handleSearchChange.bind(this);
+ this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
this.refresh = this.refresh.bind(this);
this.content = this.content.bind(this);
this.pageMenu = this.pageMenu.bind(this);
this.getItemsData();
}
- getItemsData = () => Server.get(this.state.url, false).then((data) => {
- this.setState({ current: this.state.current + this.state.dir });
- this.setState(
- { items: data.results,
- next: `${this.props.url}?page=${this.state.current + 1}`,
- prev: `${this.props.url}?page=${this.state.current - 1}`,
- loading: false,
+ getItemsData = () => {
+ const prefix = this.state.dir === 0 ? '?' : '&';
+ const searchPrefix = this.state.search !== '' ? prefix : '';
+ const searchValue = this.state.search !== '' ? `search=${this.state.search}` : '';
+ return Server.get(`${this.state.url}${searchPrefix}${searchValue}`, false).then((data) => {
+ console.log('GET ITEM DATA');
+ this.setState({
+ current: this.state.current + this.state.dir,
+ });
+ this.setState(
+ {
+ items: data.results,
+ next: `${this.props.url}?page=${this.state.current + 1}`,
+ prev: `${this.props.url}?page=${this.state.current - 1}`,
+ loading: false,
+ });
+ let first = true;
+ let last = true;
+ if (data.previous) {
+ first = false;
+ }
+ if (data.next) {
+ last = false;
+ }
+ this.setState({first, last});
+ }, error => error.then((r) => {
+ this.modalAlert.open(this.props.error, r.detail);
+ this.setState({loading: false});
+ }));
+ };
+
+ handleSearchChange(e) {
+ this.setState({ search: e.target.value });
+ if (e.target.value === '') {
+ this.setState({
+ items: [],
+ current: 1,
+ next: '',
+ prev: '',
+ url: this.props.url,
+ loading: true,
+ dir: 0,
+ start: true,
+ finish: false,
+ }, function() {
+ this.getItemsData();
});
- let first = true;
- let last = true;
- if (data.previous) {
- first = false;
- }
- if (data.next) {
- last = false;
}
- this.setState({ first, last });
- }, error => error.then((r) => {
- this.modalAlert.open(this.props.error, r.detail);
- this.setState({ loading: false });
- }));
+ }
+
+ handleSearchSubmit() {
+ this.setState({
+ items: [],
+ current: 1,
+ next: '',
+ prev: '',
+ url: this.props.url,
+ loading: true,
+ dir: 0,
+ start: true,
+ finish: false,
+ }, function() {
+ this.getItemsData();
+ });
+ }
refresh() {
this.forceUpdate();
@@ -112,6 +160,10 @@ export default class Pagination extends React.Component {
render = () => (
+
+
+
+
{ this.modalAlert = modal; }} />
{!this.state.loading && this.content()}
{!this.state.loading && this.pageMenu()}
diff --git a/core/migrations/0020_merge_20191007_0648.py b/core/migrations/0020_merge_20191007_0648.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a42b13e8acad531a1666be45051b43fca0318e4
--- /dev/null
+++ b/core/migrations/0020_merge_20191007_0648.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-06 23:48
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0017_vacancy_amount'),
+ ('core', '0019_merge_20191006_0852'),
+ ('core', '0014_auto_20191004_1340'),
+ ]
+
+ operations = [
+ ]
diff --git a/core/migrations/0021_auto_20191007_0648.py b/core/migrations/0021_auto_20191007_0648.py
new file mode 100644
index 0000000000000000000000000000000000000000..743bfd3cfe7b551934a307926873f3ceb9e76ad5
--- /dev/null
+++ b/core/migrations/0021_auto_20191007_0648.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-06 23:48
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0020_merge_20191007_0648'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='company',
+ name='website',
+ field=models.CharField(default=b'Belum ada link website', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='student',
+ name='phone_number',
+ field=models.CharField(blank=True, db_index=True, max_length=12, null=True, validators=[django.core.validators.RegexValidator(b'^0\\d{1,11}$')]),
+ ),
+ ]
diff --git a/core/migrations/0025_merge_20191007_1810.py b/core/migrations/0025_merge_20191007_1810.py
new file mode 100644
index 0000000000000000000000000000000000000000..5619c2624abb04e45e085a015e8d8bc95eb795ac
--- /dev/null
+++ b/core/migrations/0025_merge_20191007_1810.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-07 11:10
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0021_auto_20191007_0648'),
+ ('core', '0024_auto_20191007_1533'),
+ ]
+
+ operations = [
+ ]
diff --git a/core/migrations/0026_merge_20191008_0525.py b/core/migrations/0026_merge_20191008_0525.py
new file mode 100644
index 0000000000000000000000000000000000000000..31a03215fe2031121fa5683008856ba25469124d
--- /dev/null
+++ b/core/migrations/0026_merge_20191008_0525.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-07 22:25
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0025_merge_20191007_1810'),
+ ('core', '0025_merge_20191007_2124'),
+ ('core', '0025_merge_20191008_0048'),
+ ]
+
+ operations = [
+ ]
diff --git a/core/migrations/0027_merge_20191008_0652.py b/core/migrations/0027_merge_20191008_0652.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd5f54dc9175743548528801af4f27a066c7ca5e
--- /dev/null
+++ b/core/migrations/0027_merge_20191008_0652.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2019-10-07 23:52
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0026_merge_20191008_0525'),
+ ('core', '0026_merge_20191008_0256'),
+ ]
+
+ operations = [
+ ]
diff --git a/core/tests/test_vacancies.py b/core/tests/test_vacancies.py
index 4e37048ec406da450390a5dc6b450c133cdca3aa..668444fb09ffb8a9c37dddffec020869791b0c8b 100644
--- a/core/tests/test_vacancies.py
+++ b/core/tests/test_vacancies.py
@@ -43,6 +43,37 @@ class ApplicationTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ @requests_mock.Mocker()
+ def test_application_search(self, m):
+ m.get('https://akun.cs.ui.ac.id/oauth/token/verify/?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG', json={"username": 'dummy.mahasiswa', "role": 'mahasiswa', "identity_number": '1234567890'}, status_code=200)
+ m.post('https://api.cs.ui.ac.id/authentication/ldap/v2/', json={
+ "username": "dummy.mahasiswa",
+ "nama": "Dummy Mahasiswa",
+ "state": 1,
+ "kode_org": "01.00.12.01:mahasiswa",
+ "kodeidentitas": "1234567890",
+ "nama_role": "mahasiswa"
+ }, status_code=200)
+ m.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG', json={
+ "kota_lahir": "kota_kota",
+ "tgl_lahir": "2017-12-31",
+ "program": [{
+ "nm_org": "Ilmu Informasi",
+ "angkatan": "2017"
+ }]
+ }, status_code=200)
+
+ url = '/api/login/'
+
+ response = self.client.post(url, {'username': 'dummy.mahasiswa', 'password': 'lalala', 'login-type': 'sso-ui'},
+ format='json')
+ student_id = response.data.get('student').get('id')
+
+ url = '/api/students/' + str(student_id) + '/applied-vacancies/?search=engineer'
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
@requests_mock.Mocker()
def test_application_create_and_delete(self, m):
m.get('https://akun.cs.ui.ac.id/oauth/token/verify/?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG', json={"username": 'dummy.mahasiswa', "role": 'mahasiswa', "identity_number": '1234567890'}, status_code=200)
@@ -152,6 +183,42 @@ class BookmarkApplicationTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ @requests_mock.Mocker()
+ def test_search_bookmarked_application(self, m):
+ m.get('https://akun.cs.ui.ac.id/oauth/token/verify/?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG',
+ json={"username": 'dummy.mahasiswa', "role": 'mahasiswa', "identity_number": '1234567890'},
+ status_code=200)
+ m.post('https://api.cs.ui.ac.id/authentication/ldap/v2/', json={
+ "username": "dummy.mahasiswa",
+ "nama": "Dummy Mahasiswa",
+ "state": 1,
+ "kode_org": "01.00.12.01:mahasiswa",
+ "kodeidentitas": "1234567890",
+ "nama_role": "mahasiswa"
+ }, status_code=200)
+ m.get(
+ 'https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG',
+ json={
+ "kota_lahir": "kota_kota",
+ "tgl_lahir": "2017-12-31",
+ "program": [{
+ "nm_org": "Ilmu Informasi",
+ "angkatan": "2017"
+ }]
+ }, status_code=200)
+
+ url = '/api/login/'
+
+ response = self.client.post(url,
+ {'username': 'dummy.mahasiswa', 'password': 'lalala', 'login-type': 'sso-ui'},
+ format='json')
+ student_id = response.data.get('student').get('id')
+
+ url = '/api/students/' + str(student_id) + '/bookmarked-vacancies/?search=engineer'
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
@requests_mock.Mocker()
def test_application_create_and_delete(self, m):
m.get('https://akun.cs.ui.ac.id/oauth/token/verify/?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG', json={"username": 'dummy.mahasiswa', "role": 'mahasiswa', "identity_number": '1234567890'}, status_code=200)
@@ -202,6 +269,44 @@ class VacancyTest(APITestCase):
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ @requests_mock.Mocker()
+ def test_search_vacancy(self, m):
+ superuser = User.objects.create_user('dummy.company', 'dummy.company@company.com', 'lalala123')
+ company = Company.objects.create(user=superuser, description="This is a test company")
+ Vacancy.objects.create(company=company, open_time=datetime.now(), close_time=datetime.now(), name="Software Engineer")
+ Vacancy.objects.create(company=company, open_time=datetime.now(), close_time=datetime.now(), name="Data Engineer")
+ m.get('https://akun.cs.ui.ac.id/oauth/token/verify/?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG',
+ json={"username": 'dummy.mahasiswa', "role": 'mahasiswa', "identity_number": '1234567890'},
+ status_code=200)
+ m.post('https://api.cs.ui.ac.id/authentication/ldap/v2/', json={
+ "username": "dummy.mahasiswa",
+ "nama": "Dummy Mahasiswa",
+ "state": 1,
+ "kode_org": "01.00.12.01:mahasiswa",
+ "kodeidentitas": "1234567890",
+ "nama_role": "mahasiswa"
+ }, status_code=200)
+ m.get(
+ 'https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890?client_id=X3zNkFmepkdA47ASNMDZRX3Z9gqSU1Lwywu5WepG',
+ json={
+ "kota_lahir": "kota_kota",
+ "tgl_lahir": "2017-12-31",
+ "program": [{
+ "nm_org": "Ilmu Informasi",
+ "angkatan": "2017"
+ }]
+ }, status_code=200)
+
+ url = '/api/login/'
+
+ self.client.post(url, {'username': 'dummy.mahasiswa', 'password': 'lalala', 'login-type': 'sso-ui'}, format='json')
+
+ url = '/api/vacancies/?search=software'
+ response = self.client.get(url, format='json')
+ self.assertEqual(1, Company.objects.all().count())
+ self.assertEqual(2, Vacancy.objects.all().count())
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
def test_unverified_vacancy_list(self):
superuser = User.objects.create_superuser('dummy.company', 'dummy.company@company.com', 'lalala123')
self.client.force_authenticate(user=superuser)
diff --git a/core/views/vacancies.py b/core/views/vacancies.py
index e4d0d6674e3973398c3f63e7a6e5400cf7b3f494..46f79ab776bfc6a38f6aac09a77cc070d8e8c23a 100644
--- a/core/views/vacancies.py
+++ b/core/views/vacancies.py
@@ -1,6 +1,7 @@
import requests
from django.utils import timezone
from django.conf import settings
+from django.db.models import Q
from rest_framework import viewsets, status
from rest_framework.decorators import detail_route, permission_classes
from rest_framework.exceptions import ValidationError
@@ -37,8 +38,12 @@ class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet):
return super(VacancyViewSet, self).get_permissions()
def list(self, request, *args, **kwargs):
- vacancies = Vacancy.objects.all()
verified = request.query_params['verified'] if 'verified' in request.query_params else "True"
+ 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()
companies = [int(x) for x in request.query_params.getlist('company', [])]
if verified.lower() in ("yes", "true", "t", "1"):
vacancies = vacancies.filter(verified=True)
@@ -57,6 +62,7 @@ class VacancyViewSet(MultiSerializerViewSetMixin, viewsets.ModelViewSet):
page = self.paginate_queryset(vacancies)
if page is not None:
return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)
+ vacancies = Vacancy.objects.all()
return Response(VacancySerializer(vacancies, many=True, context={'request': request}).data)
def name_position_validator(self, names):
@@ -185,7 +191,11 @@ class StudentApplicationViewSet(viewsets.GenericViewSet):
"""
student = get_object_or_404(Student.objects.all(), pk=student_id)
vacancy_ids = Application.objects.filter(student=student).values('vacancy')
- vacancies = Vacancy.objects.filter(id__in=vacancy_ids)
+ 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)
page = self.paginate_queryset(vacancies)
if page is not None:
return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)
@@ -355,7 +365,11 @@ class BookmarkedVacancyByStudentViewSet(viewsets.GenericViewSet):
---
"""
student = get_object_or_404(Student.objects.all(), pk=student_id)
- vacancies = student.bookmarked_vacancies.all()
+ 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()
page = self.paginate_queryset(vacancies)
if page is not None:
return self.get_paginated_response(VacancySerializer(page, many=True, context={'request': request}).data)