From 97a774750065196c5d3ac4f44b6511601faf9558 Mon Sep 17 00:00:00 2001 From: Satryaji Aulia <satraul@macports.org> Date: Sun, 6 Oct 2019 15:40:32 +0700 Subject: [PATCH 1/2] Unit test now checks for invalid NPM --- core/tests/test_accounts.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/tests/test_accounts.py b/core/tests/test_accounts.py index d606b626..4233ec9f 100644 --- a/core/tests/test_accounts.py +++ b/core/tests/test_accounts.py @@ -2,6 +2,7 @@ import requests_mock from rest_framework import status from rest_framework.test import APIClient, APITestCase from django.contrib.auth.models import User +from django.core.exceptions import ValidationError from core.models.accounts import Company, Supervisor, Student @@ -103,6 +104,16 @@ class RegisterTests(APITestCase): response = self.client.post(url, {'username': 'lalala'}, format='multipart') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + def test_npm_validator(self): + new_user = User.objects.create_user('dummy.student2', 'dummy.student@student.com', 'lalala123') + short_npm = Student(user=new_user, npm="123456789") + self.assertRaises(ValidationError, short_npm.full_clean) + wrong_checksum = Student(user=new_user, npm="1234567890") + self.assertRaises(ValidationError, wrong_checksum.full_clean) + right_checksum = Student(user=new_user, npm="1234567894") + right_checksum.full_clean() + + class ProfileUpdateTests(APITestCase): -- GitLab From 810e0055646b7b1720176d25179a283968b86de4 Mon Sep 17 00:00:00 2001 From: Satryaji Aulia <satraul@macports.org> Date: Sun, 6 Oct 2019 15:40:59 +0700 Subject: [PATCH 2/2] Implement custom validator for NPM We now validate NPM length and checksum for Student's npm field --- core/lib/validators.py | 27 ++++++++++++++++++++++ core/migrations/0014_auto_20191004_1340.py | 21 +++++++++++++++++ core/models/accounts.py | 4 ++-- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 core/migrations/0014_auto_20191004_1340.py diff --git a/core/lib/validators.py b/core/lib/validators.py index 4aaa2242..54c9b3f3 100644 --- a/core/lib/validators.py +++ b/core/lib/validators.py @@ -1,4 +1,5 @@ import os +import math from django.core.exceptions import ValidationError from kape.settings import MAX_UPLOAD_SIZE @@ -18,3 +19,29 @@ def validate_document_file_extension(value): def validate_image_file_extension(value): validate_file(value, ['.jpeg', '.jpg', '.png', '.JPG', '.JPEG']) + + +def validate_npm(value): + ''' + NPM UI terdiri dari 10 digit, misalnya: 1234567894. Digit terakhir + (disebut checksum) dapat dihitung dari sembilan digit pertama. + Checksum dipakai untuk deteksi error pada nomor ID seperti NPM. + Digit-digit tersebut diberi indeks: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + sesuai posisinya. Kita asosiasikan digit-digit tersebut dengan + variabel-variabel d[0], d[1], d[2] , ..., d[9]. + Checksum C (yaitu d[9]) dihitung dengan rumus sebagai berikut: + 1) a = 3* (d[0] + d[2] + d[4] + d[6] + d[8]) + 2) b = (d[1] + d[3] + d[5] + d[7]) + 3) C = (a + b) % 7 + + Sebagai contoh, checksum untuk NPM 1234567894 adalah + C = (3*(1 + 3 + 5 + 7 + 9) + (2 + 4 + 6 + 8)) % 7 = 95 % 7 = 4 + yang memang ternyata benar. + + Source: Lim Yohanes Stefanus, Drs., M.Math., Ph.D + ''' + if math.ceil(math.log(value+1, 10)) != 10: + raise ValidationError(u"NPM must be 10 digits") + val_string = str(value) + if sum([3*int(a) for a in val_string[:-1:2]]+[int(a) for a in val_string[1:-1:2]]) % 7 != int(val_string[-1]): + raise ValidationError(u"NPM {} has invalid checksum".format(value)) \ No newline at end of file diff --git a/core/migrations/0014_auto_20191004_1340.py b/core/migrations/0014_auto_20191004_1340.py new file mode 100644 index 00000000..fdff75bf --- /dev/null +++ b/core/migrations/0014_auto_20191004_1340.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2019-10-04 06:40 +from __future__ import unicode_literals + +import core.lib.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0013_auto_20170602_1130'), + ] + + operations = [ + migrations.AlterField( + model_name='student', + name='npm', + field=models.IntegerField(unique=True, validators=[core.lib.validators.validate_npm]), + ), + ] diff --git a/core/models/accounts.py b/core/models/accounts.py index 2ada2804..b54f46b6 100644 --- a/core/models/accounts.py +++ b/core/models/accounts.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import User from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from core.lib.validators import validate_document_file_extension, validate_image_file_extension +from core.lib.validators import validate_document_file_extension, validate_image_file_extension, validate_npm def get_student_resume_file_path(instance, filename): @@ -53,7 +53,7 @@ class Student(models.Model): created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) user = models.OneToOneField(User) - npm = models.IntegerField(validators=[MinValueValidator(100000000), MaxValueValidator(9999999999)], unique=True) + npm = models.IntegerField(validators=[validate_npm], unique=True) resume = models.FileField(upload_to=get_student_resume_file_path, null=True, blank=True, validators=[validate_document_file_extension]) phone_number = models.CharField(max_length=100, blank=True, db_index=True, null=True) bookmarked_vacancies = models.ManyToManyField('core.Vacancy', related_name="bookmarked_vacancies", blank=True) -- GitLab