diff --git a/assets/css/custom.css b/assets/css/custom.css index 2277100c4acb37fd4c1983f0a0982bcd559a1c41..42b8b0e5f276618c553f613333c1113c34992cb0 100755 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -163,3 +163,21 @@ card .formRegis{ color: black; } +.biodata h5{ + line-height: 30%; +} + +.button-profile{ + margin-top:30px; +} + +.ui.segment.profile-form{ + padding-bottom: 37px; + margin-top:4%; +} + +.profilePage{ + margin-bottom:40px; + margin-left:5%; + margin-right:5%; +} \ No newline at end of file diff --git a/assets/js/ProfilePage.jsx b/assets/js/ProfilePage.jsx new file mode 100644 index 0000000000000000000000000000000000000000..eb331946cbd3fc6c03440b5cddaad96858616c61 --- /dev/null +++ b/assets/js/ProfilePage.jsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { Segment, Image, Header, Icon, Container, Button, Form } from 'semantic-ui-react'; +import Server from './lib/Server'; + +export default class ProfilePage extends React.Component { + + static propTypes = { + route: React.PropTypes.object.isRequired, + params: React.PropTypes.object.isRequired, + }; + + constructor(props) { + super(props); + /* istanbul ignore next */ + this.state = { + id: '', + npm: '', + name: '', + major: '', + batch: '', + email: '', + cityOfBirth: '', + dateOfBirth: '', + resume: '', + phone: '', + showTranscript: '', + }; + this.getProfile = this.getProfile.bind(this); + this.handleChange = this.handleChange.bind(this); + this.getProfile(); + } + + getProfile() { + if (this.props.route.own) { + // check api format in /api#!/login + this.state = { + id: this.props.route.data.student.id, + npm: this.props.route.data.student.npm, + name: this.props.route.data.student.name, + major: this.props.route.data.student.major, + batch: this.props.route.data.student.batch, + email: this.props.route.data.student.user.email, + cityOfBirth: this.props.route.data.student.birth_place, + dateOfBirth: this.props.route.data.student.birth_date, + resume: this.props.route.data.student.resume, + phone: this.props.route.data.student.phone_number, + showTranscript: this.props.route.data.student.show_transcript, + }; + return Promise.resolve(this.state); + } else { + return Server.get(`/students/${this.props.params.id}/`).then((data) => { + this.setState({ + id: data.id, + name: data.name, + npm: data.npm, + resume: data.resume, + major: data.major, + batch: data.batch, + email: data.user.email, + cityOfBirth: data.birth_place, + dateOfBirth: data.birth_date, + phone: data.phone_number, + showTranscript: data.show_transcript, + }); + }, error => error.then(() => { + // this.modalAlert.open('Gagal Mengambil ', r.error); + this.state.name = 'Gagal mendapatkan informasi'; + })); + } + } + + handleChange = (e) => { + this.setState({ [e.target.name]: e.target.value }); + }; + + updateForm(show) { + if (show) { + return ( + <Segment className="profile-form"> + <Header as="h3" textAlign="center"> + <Icon name="edit" /> + <Header.Content> + Edit Profile Page + </Header.Content> + </Header> + <Form size="small" onSubmit={this.handleSubmit}> + <Form.Field> + <label htmlFor="photo">Profile Picture</label> + <input onChange={this.handleChange} placeholder="Profile Photo.jpg" name="photo" type="File" /> + </Form.Field> + <Form.Field> + <label htmlFor="email">Email</label> + <input onChange={this.handleChange} placeholder="jojon@email.com" name="email" /> + </Form.Field> + <Form.Field> + <label htmlFor="phone">No. Hp</label> + <input onChange={this.handleChange} placeholder="08123456789" name="phone" /> + </Form.Field> + <Form.Field> + <label htmlFor="resume">Resume</label> + <input onChange={this.handleChange} placeholder="Resume" name="resume" type="File" /> + </Form.Field> + <Button type="submit" size="small" primary floated="right">Submit</Button> + </Form> + </Segment> + ); + } + + return (<div />); + } + + render() { + return ( + <div className="profilePage"> + <Segment className="biodata-section"> + <Header as="h2" icon textAlign="center"> + <Image src="http://semantic-ui.com/images/wireframe/square-image.png" size="small" shape="circular" /> + </Header> + <Container textAlign="center" className="profile-biodata"> + <div className="biodata"> + <h3> { this.state.name } </h3> + <h5> { this.state.major }, { this.state.batch } </h5> + <h5> { this.state.email } </h5> + <h5> { this.state.phone } </h5> + <h5> { this.state.cityOfBirth}, { this.state.dateOfBirth } </h5> + </div> + <div className="button-profile"> + <Button primary size="small">Resume</Button> + { this.state.showTranscript ? <Button primary size="small">Transkrip</Button> : <div /> } + </div> + </Container> + </Segment > + { this.updateForm(this.props.route.own) } + </div> + + ); + } +} diff --git a/assets/js/TranscriptPage.jsx b/assets/js/TranscriptPage.jsx new file mode 100644 index 0000000000000000000000000000000000000000..69da971a04dc9ff29a685018992d8d78965c93d9 --- /dev/null +++ b/assets/js/TranscriptPage.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import CourseList from './components/CourseList'; + +export default class TranscriptPage extends React.Component { + constructor(props) { + super(props); + /* istanbul ignore next */ + this.state = { + data: {"transcript":[{ + "url": "http://api.cs.ui.ac.id/siakngcs/riwayat-mahasiswa/99731/", + "npm": "1406622616", + "kelas": { + "url": "http://api.cs.ui.ac.id/siakngcs/kelas/473569/", + "kd_kls": "473569", + "nm_kls": "MD 1 - B", + "nm_mk_cl": { + "url": "http://api.cs.ui.ac.id/siakngcs/matakuliah/817/", + "kd_mk": "CSF1600100", + "nm_mk": "Matematika Diskret 1", + "kd_org": "06.00.12.01", + "kd_kur": "06.00.12.01-2012", + "jml_sks": 3 + }, + "kd_kur_cl": "06.00.12.01-2012", + "kd_mk_cl": "CSF1600100", + "periode": { + "url": "http://api.cs.ui.ac.id/siakngcs/periode/25/", + "term": 1, + "tahun": 2014 + }, + "pengajar": [ + { + "url": +"http://api.cs.ui.ac.id/siakngcs/dosen/196111251992031001/", + "nomor": "196111251992031001", + "nama": "Prof. Drs. T. Basaruddin M.Sc., Ph.D", + "id_skema": 1, + "nm_skema": "Skema Inti", + "maks_sks": 8 + } + ] + }, + "kd_kls": "473569", + "kd_kur": "06.00.12.01-2012", + "kd_mk": "CSF1600100", + "kd_org": "01.00.12.01", + "term": 1, + "tahun": 2014, + "nilai": "A" + }],"name":"Si jagoan neon" }};//ambil dari database + + } + + render() { + return ( + <CourseList data={this.state.data.transcript} name={this.state.data.name}/> + ); + } +} diff --git a/assets/js/__test__/ProfilePage-test.jsx b/assets/js/__test__/ProfilePage-test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6c725f5b61d4f2fdb5b07649ee42465ccc2d5ab7 --- /dev/null +++ b/assets/js/__test__/ProfilePage-test.jsx @@ -0,0 +1,94 @@ +/* eslint-disable no-unused-expressions */ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import fetchMock from 'fetch-mock'; +import ProfilePage from '../ProfilePage'; + +describe('ProfilePage', () => { + const studentSession = { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + company: null, + supervisor: null, + student: { + id: 3, + user: { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + }, + name: 'Muhammad R.', + created: '2017-03-28T13:33:46.147241Z', + updated: '2017-03-28T13:33:46.148248Z', + npm: 1406543593, + resume: null, + phone_number: null, + birth_place: null, + birth_date: null, + major: null, + batch: null, + show_resume: false, + bookmarked_vacancies: [ + 3, + 2, + ], + applied_vacancies: [ + 3, + 1, + ], + }, + }; + + const brokenSession = { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + company: null, + supervisor: null, + student: null, + }; + + const response = { + id: 3, + name: 'Muhammad R.', + user: { + url: 'http://localhost:8000/api/users/9/', + username: 'muhammad.reza42', + email: 'muhammad.reza42@ui.ac.id', + is_staff: false, + }, + npm: 1406543593, + resume: null, + phone_number: null, + birth_place: null, + birth_date: null, + major: null, + batch: null, + show_transcript: false, + }; + + it('renders without problem', () => { + const profile = ReactTestUtils.renderIntoDocument( + <ProfilePage route={{ own: true, data: studentSession }} params={{}} />); + expect(profile.state.name).to.equal(studentSession.student.name); + }); + + it('get profile for company without problem', () => { + fetchMock.get('*', response); + const profile = ReactTestUtils.renderIntoDocument( + <ProfilePage route={{ own: false, data: studentSession }} params={{ id: 3 }} />); + profile.getProfile().then(()=> expect(profile.state.name).to.equal(response.name)); + fetchMock.restore(); + }); + + it('renders without problem when error getting data', () => { + fetchMock.get('*', 400); + const profile = ReactTestUtils.renderIntoDocument( + <ProfilePage route={{ own: false, data: studentSession }} params={{ id: 3 }} />); + profile.getProfile().then(()=> expect(profile.state.name).to.equal('Gagal mendapatkan informasi')); + }); +}); diff --git a/assets/js/components/Course.jsx b/assets/js/components/Course.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8713846243fb6e9d167db438a28b7a81191a717b --- /dev/null +++ b/assets/js/components/Course.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Table } from 'semantic-ui-react'; + +export default class Course extends React.Component { + static propTypes = { + courseName: React.PropTypes.string.isRequired, + grade: React.PropTypes.string.isRequired, + }; + + + render() { + return ( + + <Table.Row> + <Table.Cell>{this.props.courseName}</Table.Cell> + <Table.Cell>{this.props.grade}</Table.Cell> + </Table.Row> + ); + } +} diff --git a/assets/js/components/CourseList.jsx b/assets/js/components/CourseList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..cc0357a036eb34c558ba011254d91822636fe4a4 --- /dev/null +++ b/assets/js/components/CourseList.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Grid, Segment, Table } from 'semantic-ui-react'; +import Course from './Course'; +export default class CourseList extends React.Component { + + static propTypes = { + data: React.PropTypes.array.isRequired, + name: React.PropTypes.array.isRequired, + }; + + constructor(props) { + super(props); + /* istanbul ignore next */ + this.state = { course: this.props.data }; + } + + + generateCourse() { + return this.state.course.map((course, index) => + <Course + key={index} + courseName={course.kelas.nm_kls} + grade={course.nilai} + />, + ); + } + + render = () => ( + <Grid.Column centered> + + <Segment> + <h2>Nama : {this.props.name}</h2> + <Table unstackable> + <Table.Header> + <Table.Row> + <Table.HeaderCell>Mata Kuliah</Table.HeaderCell> + <Table.HeaderCell>Nilai</Table.HeaderCell> + </Table.Row> + </Table.Header> + + <Table.Body> + { this.generateCourse() } + </Table.Body> + </Table> + </Segment> + </Grid.Column> + ); +} diff --git a/assets/js/index.jsx b/assets/js/index.jsx index 9f0d2999130144c7a4fbfc3a5517b4ad8124f49e..0d8360d6e437e2f8047784e5e4c910e176ec1d59 100644 --- a/assets/js/index.jsx +++ b/assets/js/index.jsx @@ -79,8 +79,6 @@ export default class App extends React.Component { }; render() { - // const student = this.authorization(['admin', 'student']); - // const supervisor = this.authorization(['admin', 'supervisor']); const company = this.authorization(['admin', 'company']); const commonUser = this.authorization(['admin', 'student', 'company']); diff --git a/core/lib/permissions.py b/core/lib/permissions.py index d17b4bb2cd8302b0b7441b69967ac591d9634676..2b3b549a80ab61a262d827e9aad979a6f93f2308 100644 --- a/core/lib/permissions.py +++ b/core/lib/permissions.py @@ -18,6 +18,10 @@ def is_admin_or_supervisor(user): return user.is_superuser or hasattr(user, "supervisor") +def is_admin_or_supervisor_or_company(user): + return user.is_superuser or hasattr(user, "supervisor") or hasattr(user, "company") + + class IsAdminOrSelfOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: @@ -96,6 +100,30 @@ class IsAdminOrCompany(permissions.BasePermission): return hasattr(user, "company") and user.company == company +class IsAdminOrSupervisorOrCompany(permissions.BasePermission): + def has_permission(self, request, view): + return is_admin_or_supervisor_or_company(request.user) + + +class IsAdminOrSupervisorOrCompanyOrSelf(permissions.IsAuthenticated): + def has_object_permission(self, request, view, obj): + user = request.user + if user.is_superuser or hasattr(user, "company") or hasattr(user, "supervisor"): + return True + if hasattr(user, "student"): + if isinstance(obj, Student): + student = obj + elif hasattr(obj, "student"): + student = obj.student + else: + raise APIException( + "Checking student permission on object {} not associated with Student" + .format(type(obj.__name__)) + ) + return hasattr(user, "student") and user.student == student + return False + + class IsAdminOrVacancyOwner(permissions.BasePermission): def has_permission(self, request, view): return is_admin_or_company(request.user) diff --git a/core/migrations/0007_auto_20170424_0720.py b/core/migrations/0007_auto_20170424_0720.py new file mode 100644 index 0000000000000000000000000000000000000000..23313a738e6ba8ff5cae98f8c9863bd17693eb6a --- /dev/null +++ b/core/migrations/0007_auto_20170424_0720.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-04-24 07:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_auto_20170328_1950'), + ] + + operations = [ + migrations.AddField( + model_name='student', + name='batch', + field=models.CharField(blank=True, max_length=4, null=True), + ), + migrations.AddField( + model_name='student', + name='birth_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='student', + name='birth_place', + field=models.TextField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='student', + name='major', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='student', + name='show_resume', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/migrations/0008_auto_20170424_0725.py b/core/migrations/0008_auto_20170424_0725.py new file mode 100644 index 0000000000000000000000000000000000000000..bacc0ebe121ce7f5e5151b5c9b807c42d3de2d34 --- /dev/null +++ b/core/migrations/0008_auto_20170424_0725.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-04-24 07:25 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0007_auto_20170424_0720'), + ] + + operations = [ + migrations.AlterField( + model_name='student', + name='birth_place', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AlterField( + model_name='student', + name='major', + field=models.CharField(blank=True, max_length=30, null=True), + ), + ] diff --git a/core/migrations/0009_auto_20170424_0909.py b/core/migrations/0009_auto_20170424_0909.py new file mode 100644 index 0000000000000000000000000000000000000000..151a05cfde176c36d4db2d6f3f9f9b551d61384e --- /dev/null +++ b/core/migrations/0009_auto_20170424_0909.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-04-24 09:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_auto_20170424_0725'), + ] + + operations = [ + migrations.RenameField( + model_name='student', + old_name='show_resume', + new_name='show_transcript', + ), + ] diff --git a/core/migrations/0010_student_photo.py b/core/migrations/0010_student_photo.py new file mode 100644 index 0000000000000000000000000000000000000000..2ed279ad7e922b03b5af6ee802dde16189e4a56f --- /dev/null +++ b/core/migrations/0010_student_photo.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-04-24 13:34 +from __future__ import unicode_literals + +import core.models.accounts +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_auto_20170424_0909'), + ] + + operations = [ + migrations.AddField( + model_name='student', + name='photo', + field=models.FileField(blank=True, null=True, upload_to=core.models.accounts.get_student_photo_file_path), + ), + ] diff --git a/core/migrations/0011_merge_20170425_2214.py b/core/migrations/0011_merge_20170425_2214.py new file mode 100644 index 0000000000000000000000000000000000000000..c255ca51038829cd59f9ae8d8131e5a74ed516be --- /dev/null +++ b/core/migrations/0011_merge_20170425_2214.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-04-25 22:14 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_student_photo'), + ('core', '0007_auto_20170425_1550'), + ] + + operations = [ + ] diff --git a/core/models/accounts.py b/core/models/accounts.py index 296851594da3f3abaad2c1f59f9201e9e9de2c47..7fd1d71681bf91579b31a4fa45545d884b4ed0ad 100644 --- a/core/models/accounts.py +++ b/core/models/accounts.py @@ -12,6 +12,12 @@ def get_student_resume_file_path(instance, filename): return os.path.join("student-resume/", filename) +def get_student_photo_file_path(instance, filename): + extension = filename.split('.')[-1].lower() + filename = "%s.%s" % (uuid.uuid4(), extension) + return os.path.join("student-photo/", filename) + + def get_company_logo_file_path(instance, filename): extension = filename.split('.')[-1].lower() filename = "%s.%s" % (uuid.uuid4(), extension) @@ -51,11 +57,21 @@ class Student(models.Model): bookmarked_vacancies = models.ManyToManyField('core.Vacancy', related_name="bookmarked_vacancies", blank=True) applied_vacancies = models.ManyToManyField('core.Vacancy', related_name="applied_vacancies", blank=True, through='core.Application') + birth_place = models.CharField(max_length=30, blank=True, null=True) + birth_date = models.DateField(blank=True, null=True) + major = models.CharField(max_length=30, blank=True, null=True) + batch = models.CharField(max_length=4, blank=True, null=True) + show_transcript = models.BooleanField(default=False) + photo = models.FileField(upload_to=get_student_photo_file_path, null=True, blank=True) @property def name(self): return get_display_name(self.user) + @property + def full_name(self): + return get_display_name(self.user, full_name=True) + def __unicode__(self): return u"Student {}".format(get_display_name(self.user)) diff --git a/core/serializers/accounts.py b/core/serializers/accounts.py index 63f67d1aeb70c79943b47001cbcfc4e7393f19b1..25440942f2b519fd502ebb6dac6b6eb1d3b84b6e 100644 --- a/core/serializers/accounts.py +++ b/core/serializers/accounts.py @@ -16,7 +16,41 @@ class StudentSerializer(serializers.ModelSerializer): class Meta: model = Student - fields = '__all__' + fields = ['id', 'name', 'user', 'npm', 'resume', 'phone_number', 'birth_place', 'birth_date', 'major', 'batch', \ + 'show_transcript', 'photo'] + + +class StudentUpdateSerializer(serializers.ModelSerializer): + email = serializers.EmailField() + + def to_representation(self, instance): + resume = None + photo = None + if instance.resume and hasattr(instance.resume, 'url'): + resume = instance.resume.url + if instance.photo and hasattr(instance.photo, 'url'): + photo = instance.photo.url + return { + 'resume' : resume, + 'email' : instance.user.email, + 'phone_number' : instance.phone_number, + 'photo' : photo, + 'show_transcript' : instance.show_transcript + } + + def update(self, instance, validated_data): + instance.resume = validated_data.get('resume', instance.resume) + instance.show_transcript = validated_data.get('show_transcript', instance.show_transcript) + instance.phone_number = validated_data.get('phone_number', instance.phone_number) + instance.photo = validated_data.get('photo', instance.photo) + instance.user.email = validated_data.get('email', instance.user.email) + instance.save() + instance.user.save() + return instance + + class Meta: + model = Student + fields = ['resume', 'email', 'phone_number', 'photo', 'show_transcript'] class CompanySerializer(serializers.ModelSerializer): diff --git a/core/tests/test_accounts.py b/core/tests/test_accounts.py index fe6aa42c32287d7f478ce8a647bcce67f4b7dec5..e4889f544f125743816dbbf53186f6e55b88d359 100644 --- a/core/tests/test_accounts.py +++ b/core/tests/test_accounts.py @@ -2,13 +2,12 @@ import requests_mock from rest_framework import status from rest_framework.test import APIClient, APITestCase from django.contrib.auth.models import User -from core.models.accounts import Company, Supervisor +from core.models.accounts import Company, Supervisor, Student class LoginTests(APITestCase): @requests_mock.Mocker() def test_succesful_student_login_relogin(self, m): - m.post('https://api.cs.ui.ac.id/authentication/ldap/v2/', json={ "username": "dummy.mahasiswa", "nama": "Dummy Mahasiswa", @@ -17,6 +16,14 @@ class LoginTests(APITestCase): "kodeidentitas": "1234567890", "nama_role": "mahasiswa" }, status_code=200) + m.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890/', json={ + "kota_lahir": "kota_kota", + "tgl_lahir": "2017-12-31", + "program": [{ + "nm_org" : "Ilmu Informasi", + "angkatan" : "2017" + }] + }, status_code=200) url = '/api/login/' @@ -93,3 +100,49 @@ class RegisterTests(APITestCase): url = '/api/register/' response = self.client.post(url, {'username': 'lalala'}, format='multipart') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + +class ProfileUpdateTests(APITestCase): + + @requests_mock.Mocker() + def test_student_profile_update(self, m): + 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/', 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/profiles/students/' + str(student_id) + "/" + response = self.client.patch(url, {'phone_number': '08123123123'}, format='multipart') + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + self.assertEqual(response.data.get('phone_number'), '08123123123') + + url = '/api/profiles/students/' + str(student_id) + "/" + response = self.client.patch(url, {'email': 'saasdasd'}, format='multipart') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + url = '/api/profiles/students/123123123/' + response = self.client.patch(url, {'phone_number': '08123123123'}, format='multipart') + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + new_user = User.objects.create_user('dummy.student2', 'dummy.student@student.com', 'lalala123') + new_student = Student.objects.create(user=new_user, npm="1212121212") + + url = '/api/profiles/students/' + str(new_student.pk) + "/" + response = self.client.patch(url, {'phone_number': '08123123123'}, format='multipart') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/core/tests/test_vacancies.py b/core/tests/test_vacancies.py index bb9829681f771a97e7d83a4485e0c39bbdcf2f43..a79106e84bca448cb2d50e828b05dd9475e631a6 100644 --- a/core/tests/test_vacancies.py +++ b/core/tests/test_vacancies.py @@ -20,6 +20,14 @@ class ApplicationTests(APITestCase): "kodeidentitas": "1234567890", "nama_role": "mahasiswa" }, status_code=200) + m.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890/', json={ + "kota_lahir": "kota_kota", + "tgl_lahir": "2017-12-31", + "program": [{ + "nm_org": "Ilmu Informasi", + "angkatan": "2017" + }] + }, status_code=200) url = '/api/login/' @@ -41,6 +49,14 @@ class ApplicationTests(APITestCase): "kodeidentitas": "1234567890", "nama_role": "mahasiswa" }, status_code=200) + m.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890/', json={ + "kota_lahir": "kota_kota", + "tgl_lahir": "2017-12-31", + "program": [{ + "nm_org": "Ilmu Informasi", + "angkatan": "2017" + }] + }, status_code=200) url = '/api/login/' @@ -72,6 +88,14 @@ class BookmarkApplicationTests(APITestCase): "kodeidentitas": "1234567890", "nama_role": "mahasiswa" }, status_code=200) + m.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890/', json={ + "kota_lahir": "kota_kota", + "tgl_lahir": "2017-12-31", + "program": [{ + "nm_org": "Ilmu Informasi", + "angkatan": "2017" + }] + }, status_code=200) url = '/api/login/' @@ -93,6 +117,14 @@ class BookmarkApplicationTests(APITestCase): "kodeidentitas": "1234567890", "nama_role": "mahasiswa" }, status_code=200) + m.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/1234567890/', json={ + "kota_lahir": "kota_kota", + "tgl_lahir": "2017-12-31", + "program": [{ + "nm_org": "Ilmu Informasi", + "angkatan": "2017" + }] + }, status_code=200) url = '/api/login/' diff --git a/core/views/accounts.py b/core/views/accounts.py index 3f75bb29ca402290a11f7f15d7c641b195d15c56..225d205b46ce063419a83516533a69843f33a683 100644 --- a/core/views/accounts.py +++ b/core/views/accounts.py @@ -2,18 +2,18 @@ import requests from django.contrib.auth import authenticate, login from django.contrib.auth.models import User from rest_framework import viewsets, status +from rest_framework.generics import get_object_or_404 from rest_framework.decorators import list_route from rest_framework.parsers import FormParser,MultiPartParser from rest_framework.permissions import AllowAny from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.response import Response -from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_409_CONFLICT -from core.lib.permissions import IsAdminOrStudent, IsAdminOrSelfOrReadOnly, IsAdminOrCompany, IsAdminOrSupervisor +from core.lib.permissions import IsAdminOrStudent, IsAdminOrSelfOrReadOnly, IsAdminOrCompany, IsAdminOrSupervisor, \ + IsAdminOrSupervisorOrCompanyOrSelf from core.models.accounts import Student, Company, Supervisor -from core.serializers.accounts import BasicUserSerializer, StudentSerializer, CompanySerializer, SupervisorSerializer, \ - UserSerializer, RegisterSerializer - +from core.serializers.accounts import BasicUserSerializer, UserSerializer, StudentSerializer, CompanySerializer, \ + SupervisorSerializer, RegisterSerializer, StudentUpdateSerializer class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() @@ -45,6 +45,10 @@ class StudentViewSet(viewsets.ModelViewSet): def get_permissions(self): if self.action == "update": return [IsAdminOrSelfOrReadOnly(), IsAdminOrStudent()] + if self.action == "list": + return [IsAuthenticated(), IsAdminOrSupervisor()] + if self.action == "retrieve": + return [IsAuthenticated(), IsAdminOrSupervisorOrCompanyOrSelf()] return super(StudentViewSet, self).get_permissions() @@ -93,20 +97,26 @@ class LoginViewSet(viewsets.GenericViewSet): last_name = " ".join(name) user, created = User.objects.get_or_create( username=username, - email=username + "@ui.ac.id", - first_name=first_name, - last_name=last_name + defaults={ + 'email' : username + "@ui.ac.id", + 'first_name' : first_name, + 'last_name' : last_name + } ) user.set_password(password) user.save() login(request, user) if created: if resp.get('nama_role') == "mahasiswa": + student_detail = requests.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/{}/'.format(resp.get("kodeidentitas"))) + resp_student_detail = student_detail.json() student = Student.objects.create( user=user, npm=resp.get("kodeidentitas"), - resume=None, - phone_number=None + birth_place=resp_student_detail.get('kota_lahir'), + birth_date=resp_student_detail.get('tgl_lahir'), + major=resp_student_detail.get('program')[0].get('nm_org'), + batch=resp_student_detail.get('program')[0].get('angkatan') ) student.save() else: @@ -177,7 +187,7 @@ class CompanyRegisterViewSet(viewsets.GenericViewSet): for attr in ['password', 'email', 'name', 'description', 'logo', 'address']: data[attr] = request.data.get(attr) if data[attr] is None: - return Response({'error': attr+' is required'}, status=HTTP_400_BAD_REQUEST) + return Response({'error': attr+' is required'}, status=status.HTTP_400_BAD_REQUEST) user, created = User.objects.get_or_create( username=data['email'], @@ -199,4 +209,20 @@ class CompanyRegisterViewSet(viewsets.GenericViewSet): login(request, user) return Response(serializer.data, status=status.HTTP_201_CREATED) else: - return Response({'error': 'Company with email '+data['email']+' already exist'}, status=HTTP_409_CONFLICT) + return Response({'error': 'Company with email '+data['email']+' already exist'}, status=status.HTTP_409_CONFLICT) + + +class StudentProfileViewSet(viewsets.GenericViewSet): + queryset = Student.objects.all() + permission_classes = [IsAdminOrStudent] + serializer_class = StudentUpdateSerializer + parser_classes = (MultiPartParser, FormParser,) + + def partial_update(self, request, pk=None): + user = self.get_object() + serializer = self.serializer_class(user, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/core/views/vacancies.py b/core/views/vacancies.py index 242e8f086ad4570806d12a952f391b8f34dc54ba..e26ec3d9df6ef3dbb5c16862648b5b0398de2d1d 100644 --- a/core/views/vacancies.py +++ b/core/views/vacancies.py @@ -1,3 +1,5 @@ +import requests +from django.conf import settings from rest_framework import viewsets, status from rest_framework.exceptions import ValidationError from rest_framework.generics import get_object_or_404 @@ -113,6 +115,20 @@ class CompanyApplicationStatusViewSet(viewsets.GenericViewSet): else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + def retrieve(self, request, pk=None): + application = self.get_object() + student = application.student + if student.show_transcript: + s = requests.Session() + credentials = settings.API_CS_CREDENTIALS + s.get('https://api.cs.ui.ac.id/api-auth/login/') + csrf = s.cookies['csrftoken'] + resp = s.post('https://api.cs.ui.ac.id/api-auth/login/', data={'username' : credentials["user"], 'password' : credentials["password"], 'csrfmiddlewaretoken' : csrf}) + response = s.get('https://api.cs.ui.ac.id/siakngcs/mahasiswa/' + str(student.npm) + '/riwayat/') + return Response({'name' : student.full_name, 'transcript' : response.json()}, status=status.HTTP_200_OK) + else: + return Response({'name' : student.full_name, 'error' : 'student does not allow transcript to be shown'}, status=status.HTTP_200_OK) + class CompanyVacanciesViewSet(viewsets.GenericViewSet): queryset = Vacancy.objects.all() diff --git a/kape/settings.py b/kape/settings.py index f3a865d1af8bee89cb2f1a0bfc148e6b5479694f..6a98953da08a9cfa9b39c2cea78d3d3442bb6f63 100755 --- a/kape/settings.py +++ b/kape/settings.py @@ -158,3 +158,5 @@ GZIP_CONTENT_TYPES = ( SESSION_COOKIE_HTTPONLY = False RUNNING_DEVSERVER = (len(sys.argv) > 1 and sys.argv[1] == 'runserver') + +API_CS_CREDENTIALS = {'user' : 'lalala', 'password' : 'lalala'} diff --git a/kape/urls.py b/kape/urls.py index db03077136857c0f9165ffd1663e445b6ee3ca5d..da59e3aa14b08d2ed1e43f55f6f1a985d66cd6e4 100755 --- a/kape/urls.py +++ b/kape/urls.py @@ -22,7 +22,8 @@ from rest_framework import routers from rest_framework_swagger.views import get_swagger_view from core import apps -from core.views.accounts import StudentViewSet, CompanyViewSet, SupervisorViewSet, UserViewSet, LoginViewSet, CompanyRegisterViewSet +from core.views.accounts import StudentViewSet, CompanyViewSet, SupervisorViewSet, UserViewSet, LoginViewSet, \ + CompanyRegisterViewSet, StudentProfileViewSet from core.views.vacancies import VacancyViewSet, BookmarkedVacancyByStudentViewSet, ApplicationViewSet, \ CompanyApplicationViewSet, CompanyVacanciesViewSet, CompanyApplicationStatusViewSet @@ -35,6 +36,7 @@ router.register(r'supervisors', SupervisorViewSet) router.register(r'login', LoginViewSet) router.register(r'register', CompanyRegisterViewSet) router.register(r'vacancies', VacancyViewSet) +router.register(r'profiles/students', StudentProfileViewSet) router.register(r'applications', CompanyApplicationStatusViewSet) router.register(r'students/(?P<student_id>\d+)/bookmarked-vacancies', BookmarkedVacancyByStudentViewSet, base_name='bookmarked-vacancy-list')