Fakultas Ilmu Komputer UI

Commit 18eab632 authored by Muhammad Fairuzi Teguh's avatar Muhammad Fairuzi Teguh
Browse files

Merge branch 'PBI-5-cetak-formulir' into 'staging'

Pbi 5 cetak formulir

See merge request !107
parents 44338025 c74ea273
Pipeline #48845 passed with stages
in 2 minutes and 29 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"]
...@@ -15,7 +15,7 @@ import os ...@@ -15,7 +15,7 @@ import os
import dj_database_url import dj_database_url
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Application definition # Application definition
...@@ -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',
...@@ -47,6 +48,10 @@ MIDDLEWARE = [ ...@@ -47,6 +48,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 = [
......
...@@ -24,3 +24,7 @@ USE_X_FORWARDED_HOST = os.getenv('USE_X_FORWARDED_HOST', 'False') == 'True' ...@@ -24,3 +24,7 @@ USE_X_FORWARDED_HOST = os.getenv('USE_X_FORWARDED_HOST', 'False') == 'True'
USE_X_FORWARDED_PORT = os.getenv('USE_X_FORWARDED_PORT', 'False') == 'True' USE_X_FORWARDED_PORT = os.getenv('USE_X_FORWARDED_PORT', 'False') == 'True'
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
MEDIA_URL = os.getenv('MEDIA_URL', '/media/')
MEDIA_ROOT = os.getenv('MEDIA_ROOT', os.path.join(BASE_DIR, 'media'))
...@@ -13,8 +13,10 @@ Including another URLconf ...@@ -13,8 +13,10 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import include, path
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
...@@ -22,4 +24,4 @@ urlpatterns = [ ...@@ -22,4 +24,4 @@ urlpatterns = [
path('donor/', include('donor.urls')), path('donor/', include('donor.urls')),
path('acara-donor/', include('acara_donor.urls')), path('acara-donor/', include('acara_donor.urls')),
path('stok-darah/', include('stok_darah.urls')) path('stok-darah/', include('stok_darah.urls'))
] ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
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,3 +7,5 @@ django-anymail==7.0.0 ...@@ -7,3 +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
django-kronos==1.0
\ No newline at end of file
...@@ -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)
} }
}) })
...@@ -116,3 +116,6 @@ export const getStokDarah = () => axios.get(`/stok-darah/darah/`) ...@@ -116,3 +116,6 @@ export const getStokDarah = () => axios.get(`/stok-darah/darah/`)
export const postAjukanAcaraDonor = data => export const postAjukanAcaraDonor = data =>
axios.post("/acara-donor/pengajuan/", data, { mode: "cors" }) axios.post("/acara-donor/pengajuan/", data, { mode: "cors" })
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>
</> </>
)}