Fakultas Ilmu Komputer UI

Commit 8d244c97 authored by Muhammad Fairuzi Teguh's avatar Muhammad Fairuzi Teguh
Browse files

Merge branch 'staging' of...

Merge branch 'staging' of https://gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2020/ppl-c/diskominfo-d-blood/mantan-aab-d-blood into PBI-7-edukasi
parents 6014fcc1 18eab632
Pipeline #48884 passed with stages
in 1 minute and 57 seconds
...@@ -28,4 +28,6 @@ ENV DJANGO_SETTINGS_MODULE 'dblood.settings.production' ...@@ -28,4 +28,6 @@ ENV DJANGO_SETTINGS_MODULE 'dblood.settings.production'
ENV DEBUG 'False' ENV DEBUG 'False'
ENV STATIC_URL '/api/static/' ENV STATIC_URL '/api/static/'
RUN ["python", "manage.py", "installtasks"]
CMD ["gunicorn", "dblood.wsgi", "--bind", "0.0.0.0:8000"] CMD ["gunicorn", "dblood.wsgi", "--bind", "0.0.0.0:8000"]
...@@ -28,6 +28,7 @@ INSTALLED_APPS = [ ...@@ -28,6 +28,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'rest_framework', 'rest_framework',
'rest_framework_authlib', 'rest_framework_authlib',
'kronos',
'main', 'main',
'stok_darah', 'stok_darah',
'anymail', 'anymail',
...@@ -48,6 +49,10 @@ MIDDLEWARE = [ ...@@ -48,6 +49,10 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
CRON_CLASSES = [
'donor.crons.ClearFormulirDaftarDonor'
]
ROOT_URLCONF = 'dblood.urls' ROOT_URLCONF = 'dblood.urls'
TEMPLATES = [ TEMPLATES = [
......
FROM python:3.8-buster FROM python:3.8-buster
RUN apt-get update -q && \ RUN apt-get update -q && \
apt-get install -y libpq-dev python3-dev apt-get install -y libpq-dev python3-dev cron
WORKDIR /app WORKDIR /app
COPY requirements.txt /app/ COPY requirements.txt /app/
...@@ -16,5 +16,6 @@ ENV DATABASE_URL 'sqlite:///db.sqlite3' ...@@ -16,5 +16,6 @@ ENV DATABASE_URL 'sqlite:///db.sqlite3'
ENV DJANGO_SETTINGS_MODULE 'dblood.settings.staging' ENV DJANGO_SETTINGS_MODULE 'dblood.settings.staging'
ENV STATIC_URL '/staging/api/static/' ENV STATIC_URL '/staging/api/static/'
RUN ["python3", "manage.py", "collectstatic"] RUN ["python", "manage.py", "installtasks"]
CMD ["gunicorn", "dblood.wsgi", "--bind", "0.0.0.0:8000"] CMD ["gunicorn", "dblood.wsgi", "--bind", "0.0.0.0:8000"]
import glob
import os
from datetime import datetime
import kronos
from django.conf import settings
from django.core.management import BaseCommand
@kronos.register('0 0 * * *')
class Command(BaseCommand):
help = 'Clear unused formulir daftar donor'
def handle(self, *args, **options):
formulir_folder = 'formulir-daftar-donor'
all_files = glob.glob(os.path.join(settings.MEDIA_ROOT, formulir_folder, "*.pdf"))
now = datetime.now()
cnt_removed = 0
for file in all_files:
statbuf = os.stat(file)
modified_time = datetime.fromtimestamp(statbuf.st_mtime)
elapsed_time = now - modified_time
if elapsed_time.days > 1:
os.remove(file)
cnt_removed += 1
self.stdout.write(self.style.SUCCESS(
"Successfully clear {:d} unused formulir daftar donor".format(cnt_removed)))
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.utils.timezone import localtime from django.utils.timezone import localtime
......
import os
from django.conf import settings
from PIL import Image, ImageDraw, ImageFont
from main.utils.numbers import last_digit, second_last_digit
from main.utils.python import default_kwargs_with_override
resource_path = os.path.join(settings.BASE_DIR, "donor", "resources")
class FormulirDaftarDonorService:
def _draw_formulir_data_diri(self, daftar_donor, image):
font_path = os.path.join(resource_path, "DejaVuSans.ttf")
regular_font = ImageFont.truetype(font_path, 24)
unicode_font = ImageFont.truetype(font_path, 40)
user = daftar_donor.user
profile = user.profile
draw = ImageDraw.Draw(image)
kwargs = {
"fill": (0, 0, 0),
"font": regular_font
}
first_column_x = 135
second_column_x = 720
first_row_y = 265
delta_y = 42
draw.text(xy=(first_column_x, first_row_y), text=profile.id_card_no, **kwargs)
draw.text(xy=(first_column_x, first_row_y + delta_y), text=user.first_name, **kwargs)
draw.text(xy=(first_column_x, first_row_y + 2*delta_y), text=profile.address, **kwargs)
draw.text(xy=(first_column_x, first_row_y + 4*delta_y), text=profile.phone_no, **kwargs)
draw.text(xy=(first_column_x, first_row_y + 6*delta_y), text=profile.work_address, **kwargs)
draw.text(xy=(first_column_x, first_row_y + 7*delta_y),
text=profile.work_phone_no if len(profile.work_phone_no) > 6 else "", **kwargs)
draw.text(xy=(first_column_x, first_row_y + int(9.8*delta_y)), text=profile.birthplace, **kwargs)
draw.text(xy=(first_column_x + 10, first_row_y + 12*delta_y),
text=str(second_last_digit(profile.birthdate.day)), **kwargs)
draw.text(xy=(first_column_x + 10 + 50, first_row_y + 12*delta_y),
text=str(last_digit(profile.birthdate.day)), **kwargs)
draw.text(xy=(first_column_x + 160, first_row_y + 12*delta_y),
text=str(second_last_digit(profile.birthdate.month)), **kwargs)
draw.text(xy=(first_column_x + 160 + 50, first_row_y + 12*delta_y),
text=str(last_digit(profile.birthdate.month)), **kwargs)
draw.text(xy=(first_column_x + 315, first_row_y + 12*delta_y),
text=str(second_last_digit(profile.birthdate.year)), **kwargs)
draw.text(xy=(first_column_x + 315 + 50, first_row_y + 12*delta_y),
text=str(last_digit(profile.birthdate.year)), **kwargs)
draw.text(xy=(second_column_x + (125 if profile.sex == 'F' else 0), first_row_y + int(1.1*delta_y)),
text="✔", **default_kwargs_with_override(kwargs, {"font": unicode_font}))
draw.text(xy=(second_column_x, first_row_y + 2*delta_y), text=profile.village, **kwargs)
draw.text(xy=(second_column_x, first_row_y + 3*delta_y), text=profile.district, **kwargs)
draw.text(xy=(second_column_x, first_row_y + 4*delta_y), text=profile.city, **kwargs)
draw.text(xy=(second_column_x, first_row_y + 5*delta_y), text=profile.married_status, **kwargs)
def _draw_formulir_kondisi(self, daftar_donor, image):
font_path = os.path.join(resource_path, "DejaVuSans.ttf")
regular_font = ImageFont.truetype(font_path, 24)
unicode_font = ImageFont.truetype(font_path, 40)
draw = ImageDraw.Draw(image)
kwargs = {
"text": "✔",
"fill": (0, 0, 0),
"font": unicode_font
}
first_column_yes_x = 1055
first_column_no_x = 1130
second_column_yes_x = 2230
second_column_no_x = 2305
delta_y = 33
first_column_fields_with_start_y = [
{
"start_y": 300,
"fields": [
"merasa_sehat",
"minum_antibiotik",
"minum_obat_infeksi",
]
},
{
"start_y": 435,
"fields": [
"minum_aspirin",
]
},
{
"start_y": 500,
"fields": [
"sakit_kepala_dan_demam",
]
},
{
"start_y": 595,
"fields": [
"sedang_hamil",
]
},
{
"start_y": 656,
"fields": [
"donor_darah_trombosit_plasma",
"menerima_vaksinasi",
"kontak_vaksinasi",
]
},
{
"start_y": 785,
"fields": [
"donor_aferesis",
]
},
{
"start_y": 850,
"fields": [
"pernah_transfusi",
"pernah_transplasi",
"pernah_cangkok_tulang",
"pernah_tusuk_jarum_medis",
"pernah_seks_aids",
"pernah_seks_psk",
"pernah_seks_narkoba",
"pernah_seks_konsentrat",
"wanita_pernah_seks_laki_biseksual",
"pernah_seks_dengan_hepatitis",
"tinggal_dengan_hepatitis",
"punya_tato",
"punya_tindik",
"sedang_sifilis_go",
"pernah_dipenjara",
]
}
]
for column_with_start_y in first_column_fields_with_start_y:
start_y = column_with_start_y["start_y"]
fields = column_with_start_y["fields"]
for i, field in enumerate(fields):
x = first_column_yes_x if getattr(daftar_donor, field) is True else first_column_no_x
draw.text(xy=(x, start_y + i*delta_y), **kwargs)
if daftar_donor.sedang_hamil:
draw.text(xy=(950, 595), **default_kwargs_with_override(kwargs, {
"text": str(daftar_donor.kehamilan_berapa),
"font": regular_font
}))
second_column_fields_with_start_y = [
{
"start_y": 333,
"fields": [
"pernah_di_luar_indonesia",
]
},
{
"start_y": 430,
"fields": [
"pernah_jualan_seks",
]
},
{
"start_y": 500,
"fields": [
"pernah_homoseks",
]
},
{
"start_y": 590,
"fields": [
"pernah_tinggal_di_eropa",
"pernah_terima_transfusi_di_inggris",
]
},
{
"start_y": 730,
"fields": [
"pernah_tinggal_di_inggris",
]
},
{
"start_y": 820,
"fields": [
"positif_aids",
]
},
{
"start_y": 885,
"fields": [
"pakai_jarum_suntik",
"pakai_konsentrat",
"menderita_hepatitis",
"menderita_malaria",
"menderita_kanker",
"bermasalah_jantung_paru_paru",
"menderita_pendarahan",
"seks_dengan_orang_afrika",
"tinggal_di_afrika",
]
},
]
for column_with_start_y in second_column_fields_with_start_y:
start_y = column_with_start_y["start_y"]
fields = column_with_start_y["fields"]
for i, field in enumerate(fields):
x = second_column_yes_x if getattr(daftar_donor, field) is True else second_column_no_x
draw.text(xy=(x, start_y + i*delta_y), **kwargs)
def generate_formulir(self, daftar_donor):
template_data_diri_path = os.path.join(resource_path, "form-a4-data_diri.jpg")
formulir_data_diri = Image.open(template_data_diri_path).convert("RGB")
self._draw_formulir_data_diri(daftar_donor, formulir_data_diri)
template_kondisi_path = os.path.join(resource_path, "form-a4-kondisi.jpg")
formulir_kondisi = Image.open(template_kondisi_path).convert("RGB")
self._draw_formulir_kondisi(daftar_donor, formulir_kondisi)
formulir_folder = 'formulir-daftar-donor'
formulir_name = os.path.join(formulir_folder, 'formulir-' + str(daftar_donor.pk) + '.pdf')
formulir_folder_path = os.path.join(settings.MEDIA_ROOT, formulir_folder)
os.makedirs(formulir_folder_path, exist_ok=True)
formulir_path = os.path.join(settings.MEDIA_ROOT, formulir_name)
formulir_data_diri.save(formulir_path, save_all=True, append_images=[formulir_kondisi])
return settings.MEDIA_URL + formulir_name
formulir_daftar_donor_service = FormulirDaftarDonorService()
...@@ -271,3 +271,23 @@ class RiwayatDonorTest(APITestCase): ...@@ -271,3 +271,23 @@ class RiwayatDonorTest(APITestCase):
'quota': second_jadwal_donor.quota, 'quota': second_jadwal_donor.quota,
'category': second_jadwal_donor.category 'category': second_jadwal_donor.category
}]) }])
class FormulirDaftarDonorTest(APITestCase):
def setUp(self):
self.user = UserFactory(email='fairuzi@dblood.com')
self.user.save()
self.jadwal_donor = JadwalDonorFactory(time_start=timezone.localtime() - timedelta(hours=24),
time_end=timezone.localtime() - timedelta(hours=23))
self.daftar_donor = DaftarDonorFactory(
user=self.user, jadwal_donor=self.jadwal_donor, sedang_hamil=True, kehamilan_berapa=3)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + str(AccessToken.for_user(self.user)))
def test_get_formulir_authenticated(self):
self.client.credentials()
response = self.client.get('/donor/formulir-daftar/' + str(self.daftar_donor.id))
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_get_formulir_return_url(self):
response = self.client.get('/donor/formulir-daftar/' + str(self.daftar_donor.id))
self.assertIn('http', response.data['url'])
from django.urls import path from django.urls import path
from donor.views import JadwalDonorView, DaftarDonorView, AgendaDonorView, RiwayatDonorView from donor.views import JadwalDonorView, DaftarDonorView, AgendaDonorView, RiwayatDonorView, FormulirDaftarDonorView
urlpatterns = [ urlpatterns = [
path('jadwal/', JadwalDonorView.as_view()), path('jadwal/', JadwalDonorView.as_view()),
path('jadwal/<int:pk>/daftar/', DaftarDonorView.as_view()), path('jadwal/<int:pk>/daftar/', DaftarDonorView.as_view()),
path('jadwal/agenda/', AgendaDonorView.as_view()), path('jadwal/agenda/', AgendaDonorView.as_view()),
path('jadwal/riwayat/', RiwayatDonorView.as_view()) path('jadwal/riwayat/', RiwayatDonorView.as_view()),
path('formulir-daftar/<int:pk>', FormulirDaftarDonorView.as_view())
] ]
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import now from django.utils.timezone import now
from rest_framework import generics from rest_framework import generics, views
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from donor.models import DaftarDonor, JadwalDonor from donor.models import DaftarDonor, JadwalDonor
from donor.serializers import DaftarDonorSerializer, JadwalDonorSerializer from donor.serializers import DaftarDonorSerializer, JadwalDonorSerializer
from main.paginations import ExtraSmallResultsSetPagination from main.paginations import ExtraSmallResultsSetPagination
from donor.services import formulir_daftar_donor_service
class JadwalDonorView(generics.ListAPIView): class JadwalDonorView(generics.ListAPIView):
...@@ -57,3 +59,14 @@ class RiwayatDonorView(generics.ListAPIView): ...@@ -57,3 +59,14 @@ class RiwayatDonorView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
daftar_donors = DaftarDonor.objects.filter(user=self.request.user, has_attended=True) daftar_donors = DaftarDonor.objects.filter(user=self.request.user, has_attended=True)
return JadwalDonor.objects.filter(daftar_donors__in=daftar_donors).order_by('-time_start') return JadwalDonor.objects.filter(daftar_donors__in=daftar_donors).order_by('-time_start')
class FormulirDaftarDonorView(views.APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
daftar_donor = get_object_or_404(DaftarDonor, user=request.user, jadwal_donor__pk=pk)
file_path = formulir_daftar_donor_service.generate_formulir(daftar_donor)
file_url = request.build_absolute_uri(file_path)
return Response({"url": file_url})
def last_digit(n):
return n % 10
def second_last_digit(n):
return n//10 % 10
def default_kwargs_with_override(default, override):
result = default.copy()
for key in override:
result[key] = override[key]
return result
...@@ -7,4 +7,5 @@ django-anymail==7.0.0 ...@@ -7,4 +7,5 @@ django-anymail==7.0.0
djangorestframework==3.11.0 djangorestframework==3.11.0
django-cors-headers==3.2.1 django-cors-headers==3.2.1
gunicorn==20.0.4 gunicorn==20.0.4
Pillow==7.1.2 Pillow==7.1.2
\ No newline at end of file django-kronos==1.0
...@@ -9,7 +9,6 @@ axios.interceptors.response.use( ...@@ -9,7 +9,6 @@ axios.interceptors.response.use(
return response return response
}, },
error => { error => {
const [refreshToken, setRefreshToken] = useLocalStorage("refresh_token")
const originalRequest = error.config const originalRequest = error.config
if ( if (
error.response.status === 401 && error.response.status === 401 &&
...@@ -19,11 +18,12 @@ axios.interceptors.response.use( ...@@ -19,11 +18,12 @@ axios.interceptors.response.use(
console.log("DO NOTHING") console.log("DO NOTHING")
} else if (error.response.status === 401 && !originalRequest._retry) { } else if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true originalRequest._retry = true
const refreshToken = localStorage.getItem("refresh_token")
return postRefreshToken(refreshToken).then(res => { return postRefreshToken(refreshToken).then(res => {
if (res.status === 200) { if (res.status === 200) {
axios.defaults.headers.common.Authorization = axios.defaults.headers.common.Authorization =
"Bearer " + res.data.access "Bearer " + res.data.access
setRefreshToken(res.data.refresh) localStorage.setItem("refresh_token", res.data.refresh)
return axios(originalRequest) return axios(originalRequest)
} }
}) })
...@@ -121,3 +121,6 @@ export const getListArtikelEdukasi = page => ...@@ -121,3 +121,6 @@ export const getListArtikelEdukasi = page =>
axios.get("/edukasi/artikel?page=" + page) axios.get("/edukasi/artikel?page=" + page)
export const getArtikelEdukasi = id => axios.get("/edukasi/artikel/" + id) export const getArtikelEdukasi = id => axios.get("/edukasi/artikel/" + id)
export const getFormulirDaftarDonor = jadwalDonorId =>
axios.get("/donor/formulir-daftar/" + jadwalDonorId)
...@@ -2,7 +2,12 @@ import { Link } from "gatsby" ...@@ -2,7 +2,12 @@ import { Link } from "gatsby"
import moment from "moment" import moment from "moment"
import React, { useState } from "react" import React, { useState } from "react"
import { useQuery } from "react-query" import { useQuery } from "react-query"
import { getAgendaDonor, getRiwayatDonor, getUserProfile } from "../api" import {
getAgendaDonor,
getFormulirDaftarDonor,
getRiwayatDonor,
getUserProfile,
} from "../api"
import { withAuthenticatedOrRedirect } from "../components/authenticated-only" import { withAuthenticatedOrRedirect } from "../components/authenticated-only"
import ErrorRetry from "../components/error-retry" import ErrorRetry from "../components/error-retry"
import Layout from "../components/layout" import Layout from "../components/layout"
...@@ -149,6 +154,23 @@ const Profile = () => { ...@@ -149,6 +154,23 @@ const Profile = () => {
? "Umum" ? "Umum"
: "Tertutup"} : "Tertutup"}
</p> </p>
<div className="text-right">
<button
className="btn btn-large btn-red"
onClick={async () => {
try {
const {
data: { url },
} = await getFormulirDaftarDonor(agendaDonor.id)
window.open(url, "_blank")
} catch (err) {
alert(err.message)
}
}}
>
Cetak Formulir
</button>
</div>
</> </>
)} )}
</div> </div>
......
import { fireEvent, screen, waitFor } from "@testing-library/react" import { fireEvent, screen, waitFor } from "@testing-library/react"
import React from "react" import React from "react"
import { getAgendaDonor, getRiwayatDonor, getUserProfile } from "../api" import {
getAgendaDonor,
getRiwayatDonor,
getUserProfile,
getFormulirDaftarDonor,
} from "../api"
import { renderAuthenticated } from "../utils/test-util" import { renderAuthenticated } from "../utils/test-util"
import { jadwalDonorFactory } from "./jadwal-donor.factory" import { jadwalDonorFactory } from "./jadwal-donor.factory"
import ProfilePage from "./profile" import ProfilePage from "./profile"
...@@ -104,6 +109,56 @@ describe("Agenda Donor", () => { ...@@ -104,6 +109,56 @@ describe("Agenda Donor", () => {
expect(screen.queryByText("Loading...")).not.toBeInTheDocument() expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
) )
}) })
it("can print formulir", async () => {
getAgendaDonor.mockResolvedValueOnce({
data: [
{
id: 1,
kecamatan: "Beji",
location: "D'Mall",
time_start: "2020-03-02T10:00:00+07:00",
time_end: "2020-03-02T15:00:00+07:00",
quota: 150,
category: "public",
},
],
})
const printablePdfUrl = "https://printable.pdf"
getFormulirDaftarDonor.mockResolvedValueOnce({
data: {
url: printablePdfUrl,
},
})
window.open = jest.fn()
renderAuthenticated(<ProfilePage />)
fireEvent.click(await screen.findByText("Cetak Formulir"))