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'
ENV DEBUG 'False'
ENV STATIC_URL '/api/static/'
RUN ["python", "manage.py", "installtasks"]
CMD ["gunicorn", "dblood.wsgi", "--bind", "0.0.0.0:8000"]
......@@ -28,6 +28,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_authlib',
'kronos',
'main',
'stok_darah',
'anymail',
......@@ -48,6 +49,10 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CRON_CLASSES = [
'donor.crons.ClearFormulirDaftarDonor'
]
ROOT_URLCONF = 'dblood.urls'
TEMPLATES = [
......
FROM python:3.8-buster
RUN apt-get update -q && \
apt-get install -y libpq-dev python3-dev
apt-get install -y libpq-dev python3-dev cron
WORKDIR /app
COPY requirements.txt /app/
......@@ -16,5 +16,6 @@ ENV DATABASE_URL 'sqlite:///db.sqlite3'
ENV DJANGO_SETTINGS_MODULE 'dblood.settings.staging'
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"]
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.validators import MinValueValidator, MaxValueValidator
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
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):
'quota': second_jadwal_donor.quota,
'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 donor.views import JadwalDonorView, DaftarDonorView, AgendaDonorView, RiwayatDonorView
from donor.views import JadwalDonorView, DaftarDonorView, AgendaDonorView, RiwayatDonorView, FormulirDaftarDonorView
urlpatterns = [
path('jadwal/', JadwalDonorView.as_view()),
path('jadwal/<int:pk>/daftar/', DaftarDonorView.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.utils import timezone
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.permissions import IsAuthenticated
from rest_framework.response import Response
from donor.models import DaftarDonor, JadwalDonor
from donor.serializers import DaftarDonorSerializer, JadwalDonorSerializer
from main.paginations import ExtraSmallResultsSetPagination
from donor.services import formulir_daftar_donor_service
class JadwalDonorView(generics.ListAPIView):
......@@ -57,3 +59,14 @@ class RiwayatDonorView(generics.ListAPIView):
def get_queryset(self):
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')
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
djangorestframework==3.11.0
django-cors-headers==3.2.1
gunicorn==20.0.4
Pillow==7.1.2
\ No newline at end of file
Pillow==7.1.2
django-kronos==1.0
......@@ -9,7 +9,6 @@ axios.interceptors.response.use(
return response
},
error => {
const [refreshToken, setRefreshToken] = useLocalStorage("refresh_token")
const originalRequest = error.config
if (
error.response.status === 401 &&
......@@ -19,11 +18,12 @@ axios.interceptors.response.use(
console.log("DO NOTHING")
} else if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
const refreshToken = localStorage.getItem("refresh_token")
return postRefreshToken(refreshToken).then(res => {
if (res.status === 200) {
axios.defaults.headers.common.Authorization =
"Bearer " + res.data.access
setRefreshToken(res.data.refresh)
localStorage.setItem("refresh_token", res.data.refresh)
return axios(originalRequest)
}
})
......@@ -121,3 +121,6 @@ export const getListArtikelEdukasi = page =>
axios.get("/edukasi/artikel?page=" + page)
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"
import moment from "moment"
import React, { useState } from "react"
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 ErrorRetry from "../components/error-retry"
import Layout from "../components/layout"
......@@ -149,6 +154,23 @@ const Profile = () => {
? "Umum"
: "Tertutup"}
</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>
......
import { fireEvent, screen, waitFor } from "@testing-library/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 { jadwalDonorFactory } from "./jadwal-donor.factory"
import ProfilePage from "./profile"
......@@ -104,6 +109,56 @@ describe("Agenda Donor", () => {
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"))
await waitFor(() =>
expect(window.open).toHaveBeenCalledWith(printablePdfUrl, "_blank")
)
})
it("can alert when print formulir fails", 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 errorMessage = "Network error"
getFormulirDaftarDonor.mockRejectedValueOnce(new Error(errorMessage))
window.alert = jest.fn()
renderAuthenticated(<ProfilePage />)
fireEvent.click(await screen.findByText("Cetak Formulir"))
await waitFor(() => expect(window.alert).toHaveBeenCalledWith(errorMessage))
})
})
describe("Riwayat Donor", () => {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment