diff --git a/backend/acara_donor/admin.py b/backend/acara_donor/admin.py index e400381c93c02f0a196e5337bf0650f2e5e7bd5a..1236a7065d736c45016eedad6086e8c7e52cd51e 100644 --- a/backend/acara_donor/admin.py +++ b/backend/acara_donor/admin.py @@ -1,8 +1,34 @@ +from django.http import HttpResponse from django.contrib import admin from acara_donor.models import AcaraDonor from acara_donor.filters import AcaraDonorFilter +def download_acara_donor(modeladmin, request, queryset): + import pandas as pd + from django_pandas.io import read_frame + + response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = f'attachment; filename={modeladmin.model._meta}.xlsx' + + df = read_frame(queryset) + df['waktu_donor'] = df['waktu_donor'].astype(str).str[:-6] # Strip tzinfo + + writer = pd.ExcelWriter(response, engine='xlsxwriter') + + df.to_excel(writer, sheet_name='Sheet1') + + workbook = writer.book + # worksheet = writer.sheets['Sheet1'] + + workbook.close() + + return response + + +download_acara_donor.short_description = 'Download Acara Donor as Excel Workbook' + + @admin.register(AcaraDonor) class AcaraDonorAdmin(admin.ModelAdmin): list_filter = ('status', AcaraDonorFilter, 'alamat_lokasi_donor') @@ -15,3 +41,4 @@ class AcaraDonorAdmin(admin.ModelAdmin): 'nama_koor', 'email_koor', 'no_telp_koor') + actions = [download_acara_donor] diff --git a/backend/acara_donor/tests.py b/backend/acara_donor/tests.py index 8fb64bc34942ad9adea9f220d2ef322009478910..f81b4c78cb6dfb6eecc2549881c98a80148fc5ae 100644 --- a/backend/acara_donor/tests.py +++ b/backend/acara_donor/tests.py @@ -1,6 +1,8 @@ import datetime from unittest.mock import patch +from django.contrib.auth import get_user_model from django.test import TestCase +from django.urls import reverse from django.utils import timezone from rest_framework import status from rest_framework.test import APITestCase @@ -12,6 +14,9 @@ from acara_donor.admin import AcaraDonorAdmin from acara_donor.filters import AcaraDonorFilter +User = get_user_model() + + class AcaraDonorTest(TestCase): """ Test module for Acara Donor model""" @@ -135,3 +140,24 @@ class AcaraDonorFilterTest(TestCase): filter_this_year = AcaraDonorFilter(None, {'waktu_donor': 'this_year'}, AcaraDonor, AcaraDonorAdmin) acara_donor_filtered = filter_this_year.queryset(None, AcaraDonor.objects.all())[0] self.assertEqual(acara_donor_filtered.waktu_donor, self.acara_donor_next_month.waktu_donor) + + +class AcaraDonorAdminTest(TestCase): + + def setUp(self): + username = 'admin@host' + password = 'secretsauce' + User.objects.create_superuser(username, password) + self.client.login(username=username, password=password) + + def test_action_download_acaradonor(self): + user = UserFactory() + user.save() + acara_donor = AcaraDonorFactory(user=user) + acara_donor.save() + + endpoint = reverse('admin:acara_donor_acaradonor_changelist') + data = {'action': 'download_acara_donor', '_selected_action': [acara_donor.nomor]} + response = self.client.post(endpoint, data=data) + + self.assertEqual(response['content-type'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') diff --git a/backend/donor/admin.py b/backend/donor/admin.py index 93ea621e3e778ceb79261df1574aa245021db6a4..4b93a0aae497bc5ff5bf7823f0d52454c87accdd 100644 --- a/backend/donor/admin.py +++ b/backend/donor/admin.py @@ -1,11 +1,71 @@ from django.contrib import admin +from django.http import HttpResponse from donor.models import JadwalDonor, DaftarDonor +def download_jadwaldonor(modeladmin, request, queryset): + import pandas as pd + from django_pandas.io import read_frame + + response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response['Content-Disposition'] = f'attachment; filename={modeladmin.model._meta}.xlsx' + + daftar_donor = DaftarDonor.download_query(queryset) + fieldnames = [ + 'id', + 'jadwal_donor__time_start', + 'user__profile__id_card_no', + 'user__first_name', + 'user__profile__address', + 'user__profile__phone_no', + 'user__profile__blood_type', + 'user__profile__sex', + ] + df = read_frame(daftar_donor, fieldnames=fieldnames) + df = df.rename(columns={ + fieldnames[0]: "NoTrans", + fieldnames[1]: "Tanggal", + fieldnames[2]: "ID", + fieldnames[3]: "Nama Lengkap", + fieldnames[4]: "Alamat", + fieldnames[5]: "HP", + fieldnames[6]: "Gol (RH)", + fieldnames[7]: "JK", + }) + + if len(df): + df['Tanggal'] = df['Tanggal'].dt.tz_localize(None) + + writer = pd.ExcelWriter(response, engine='xlsxwriter') + df.to_excel(writer, sheet_name='Sheet1') + + workbook = writer.book + worksheet = writer.sheets['Sheet1'] + + def get_col_widths(dataframe): + # First we find the maximum length of the index column + idx_max = max([len(str(s)) for s in dataframe.index.values] + [len(str(dataframe.index.name))]) + # Then, we concatenate this to the max of the lengths of column name + # and its values for each column, left to right + return [idx_max] + [max([len(str(s)) for s in dataframe[col].values] + [len(col)]) for col in dataframe.columns] + + text_format = workbook.add_format({'text_wrap': True}) + + for i, width in enumerate(get_col_widths(df)): + worksheet.set_column(i, i, width, text_format) + + workbook.close() + return response + + +download_jadwaldonor.short_description = 'Download Jadwal Donor as Excel Workbook' + + @admin.register(JadwalDonor) class JadwalDonorAdmin(admin.ModelAdmin): list_filter = ('kecamatan', 'time_start', 'category') + actions = [download_jadwaldonor] @admin.register(DaftarDonor) diff --git a/backend/donor/models.py b/backend/donor/models.py index 59ae457a9acc1f4f93b5bf7f9d79248563ef224e..5b15049d89ef0843fbf7813b38add0d1d3262f72 100644 --- a/backend/donor/models.py +++ b/backend/donor/models.py @@ -97,10 +97,14 @@ class DaftarDonor(models.Model): seks_dengan_orang_afrika = models.BooleanField() tinggal_di_afrika = models.BooleanField() + def __str__(self): + return str(self.user) + " - " + str(self.jadwal_donor) + + @classmethod + def download_query(cls, jadwal_qs): + return cls.objects.filter(jadwal_donor__in=jadwal_qs, has_attended=True) + class Meta: constraints = [ models.UniqueConstraint(fields=['user', 'jadwal_donor'], name='unique__user-jadwal_donor') ] - - def __str__(self): - return str(self.user) + " - " + str(self.jadwal_donor) diff --git a/backend/donor/test_models.py b/backend/donor/test_models.py index d5f3163e0ff016c3c61ace3615a9c374828d3320..0336c66d8db2bd734ce4bc4a6beca69ee1a31919 100644 --- a/backend/donor/test_models.py +++ b/backend/donor/test_models.py @@ -1,9 +1,11 @@ +from datetime import datetime, timedelta from django.test import TestCase +from django.utils import timezone +from django.core.exceptions import ValidationError + from main.factories import UserFactory -from donor.models import JadwalDonor +from donor.models import JadwalDonor, DaftarDonor from donor.factories import JadwalDonorFactory, DaftarDonorFactory -from datetime import datetime -from django.core.exceptions import ValidationError class JadwalDonorTest(TestCase): @@ -35,3 +37,17 @@ class DaftarDonorTest(TestCase): jadwal_donor = JadwalDonorFactory() daftar_donor = DaftarDonorFactory(user=user, jadwal_donor=jadwal_donor) self.assertEqual(str(daftar_donor), str(user) + ' - ' + str(jadwal_donor)) + + def test_download_query(self): + user1 = UserFactory(email='donald@duckduckgo.org') + user1.save() + user2 = UserFactory(email='daisy@duckduckgo.org') + user2.save() + jadwal_donor = JadwalDonorFactory(time_start=timezone.localtime() - timedelta(hours=24), + time_end=timezone.localtime() - timedelta(hours=23)) + DaftarDonorFactory(user=user1, jadwal_donor=jadwal_donor, has_attended=True) + DaftarDonorFactory(user=user2, jadwal_donor=jadwal_donor, has_attended=True) + + qs = DaftarDonor.download_query(JadwalDonor.objects.filter(id=jadwal_donor.id)) + + self.assertEqual(len(qs), 2) diff --git a/backend/donor/tests.py b/backend/donor/tests.py index dc55f5fd23aed1aa7441e3aa7d85bfa3e1ea4312..ae1aeeded840738ac83f364671aadac0c3105d42 100644 --- a/backend/donor/tests.py +++ b/backend/donor/tests.py @@ -1,13 +1,16 @@ +from datetime import timedelta +from django.urls import reverse +from django.utils import timezone +from django.utils.timezone import localtime +from django.test import TestCase from rest_framework import status from rest_framework.test import APITestCase -from django.utils import timezone from donor.factories import JadwalDonorFactory, DaftarDonorFactory from donor.models import JadwalDonor from main.factories import UserFactory -from datetime import timedelta +from main.models import User from rest_framework_authlib.tokens import AccessToken -from django.utils.timezone import localtime class JadwalDonorTests(APITestCase): @@ -273,6 +276,28 @@ class RiwayatDonorTest(APITestCase): }]) +class JadwalDonorAdminTest(TestCase): + + def setUp(self): + username = 'admin@host' + password = 'secretsauce' + User.objects.create_superuser(username, password) + self.client.login(username=username, password=password) + + def test_action_download_jadwaldonor(self): + user = UserFactory(email='donald@duckduckgo.org') + user.save() + jadwal_donor = JadwalDonorFactory(time_start=timezone.localtime() - timedelta(hours=24), + time_end=timezone.localtime() - timedelta(hours=23)) + DaftarDonorFactory(user=user, jadwal_donor=jadwal_donor, has_attended=True) + + endpoint = reverse('admin:donor_jadwaldonor_changelist') + data = {'action': 'download_jadwaldonor', '_selected_action': [jadwal_donor.id]} + response = self.client.post(endpoint, data=data) + + self.assertEqual(response['content-type'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + + class FormulirDaftarDonorTest(APITestCase): def setUp(self): self.user = UserFactory(email='fairuzi@dblood.com') diff --git a/backend/main/models.py b/backend/main/models.py index 96b6f8ffb23a7b0755af826fa66fb6a84fa1235f..ebc6ce8424082c419c8be179f28a0fe19cdbc8dd 100644 --- a/backend/main/models.py +++ b/backend/main/models.py @@ -1,3 +1,4 @@ +from datetime import date from django.conf import settings from django.contrib.auth.models import AbstractUser, BaseUserManager from django.db import models @@ -90,6 +91,12 @@ class Profile(models.Model): work_email = models.EmailField(blank=True) work_phone_no = models.CharField(max_length=20, blank=True) + @property + def age(self): + born = self.birthdate + today = date.today() + return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + def __str__(self): return f'({self.sex}, {self.blood_type})' diff --git a/backend/requirements.txt b/backend/requirements.txt index 1e459679b7d32004c2fd739fdf537ddb0caad211..0e2f5ccf95cfbf1e525c0ef6171151209f724a2f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -6,6 +6,10 @@ Django==3.0.5 django-anymail==7.0.0 djangorestframework==3.11.0 django-cors-headers==3.2.1 +django-kronos==1.0 +django-pandas==0.6.2 gunicorn==20.0.4 +numpy==1.18.4 +pandas==1.0.4 Pillow==7.1.2 -django-kronos==1.0 +XlsxWriter==1.2.9