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