Fakultas Ilmu Komputer UI

Commit 432db4f8 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 warmfix-sonarqube-ignore
parents b6108195 2f4ce6bc
Pipeline #43680 passed with stages
in 20 minutes and 40 seconds
......@@ -167,8 +167,6 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_authlib.authentication.JWTAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100
}
# Authlib settings
......@@ -187,8 +185,14 @@ ANYMAIL = {
'MAILGUN_SENDER_DOMAIN': os.getenv('MAILGUN_SENDER_DOMAIN'),
}
DEFAULT_FROM_EMAIL = 'noreply@dblood.depok.go.id'
EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', 'django.core.mail.backends.smtp.EmailBackend')
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
SERVER_EMAIL = 'server@dblood.depok.go.id'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER or 'noreply@dblood.depok.go.id'
SERVER_EMAIL = EMAIL_HOST_USER or 'server@dblood.depok.go.id'
# Generated by Django 3.0.5 on 2020-04-27 00:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('donor', '0009_auto_20200425_2147'),
]
operations = [
migrations.AlterField(
model_name='daftardonor',
name='jadwal_donor',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='daftar_donors', to='donor.JadwalDonor'),
),
]
......@@ -51,7 +51,7 @@ class JadwalDonor(models.Model):
class DaftarDonor(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
jadwal_donor = models.ForeignKey(JadwalDonor, on_delete=models.CASCADE)
jadwal_donor = models.ForeignKey(JadwalDonor, on_delete=models.CASCADE, related_name='daftar_donors')
has_attended = models.BooleanField(default=False)
merasa_sehat = models.BooleanField()
minum_antibiotik = models.BooleanField()
......
......@@ -11,10 +11,4 @@ class JadwalDonorSerializer(serializers.ModelSerializer):
class DaftarDonorSerializer(serializers.ModelSerializer):
class Meta:
model = DaftarDonor
fields = '__all__'
validators = [
serializers.UniqueTogetherValidator(
queryset=model.objects.all(),
fields=('user', 'jadwal_donor'),
)
]
exclude = ['user', 'jadwal_donor', 'has_attended']
......@@ -253,8 +253,16 @@ class RiwayatDonorTest(APITestCase):
time_end=timezone.localtime() - timedelta(hours=43))
DaftarDonorFactory(user=self.user, jadwal_donor=second_jadwal_donor, has_attended=True)
response = self.client.get('/donor/jadwal/riwayat/?limit=1&offset=1')
response = self.client.get('/donor/jadwal/riwayat/')
self.assertEqual(response.data['results'], [{
'id': self.jadwal_donor.id,
'kecamatan': self.jadwal_donor.kecamatan,
'location': self.jadwal_donor.location,
'time_start': self.jadwal_donor.time_start.isoformat(),
'time_end': self.jadwal_donor.time_end.isoformat(),
'quota': self.jadwal_donor.quota,
'category': self.jadwal_donor.category
}, {
'id': second_jadwal_donor.id,
'kecamatan': second_jadwal_donor.kecamatan,
'location': second_jadwal_donor.location,
......
from django.urls import include, path
from rest_framework import routers
from donor.views import JadwalDonorViewSet
router = routers.DefaultRouter()
router.register(r'jadwal', JadwalDonorViewSet, basename='jadwal')
from django.urls import path
from donor.views import JadwalDonorView, DaftarDonorView, AgendaDonorView, RiwayatDonorView
urlpatterns = [
path('', include(router.urls)) # NOQA: DJ05
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())
]
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from donor.models import JadwalDonor, DaftarDonor
from django.shortcuts import get_object_or_404
from django.utils import timezone
from donor.serializers import JadwalDonorSerializer, DaftarDonorSerializer
from django.utils.timezone import now
from rest_framework import generics
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated
from donor.models import DaftarDonor, JadwalDonor
from donor.serializers import DaftarDonorSerializer, JadwalDonorSerializer
from main.paginations import SmallResultsSetPagination
class JadwalDonorViewSet(viewsets.GenericViewSet):
def list(self, request):
class JadwalDonorView(generics.ListAPIView):
serializer_class = JadwalDonorSerializer
def get_queryset(self):
queryset = JadwalDonor.objects.all()
date = request.query_params.get('date', None)
date = self.request.query_params.get('date', None)
if date is not None:
queryset = queryset.filter(time_start__date=date)
queryset = queryset.order_by('time_start')
serializer = JadwalDonorSerializer(queryset, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def daftar(self, request, pk=None):
if request.user.is_authenticated:
jadwal_donor = get_object_or_404(JadwalDonor, pk=pk)
if jadwal_donor.time_end < now():
return Response(status=status.HTTP_400_BAD_REQUEST)
data = request.data.copy()
data['user'] = request.user.id
data['jadwal_donor'] = jadwal_donor.id
serializer = DaftarDonorSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)
@action(detail=False, methods=['get'])
def agenda(self, request):
if request.user.is_authenticated:
list_daftar_donor = DaftarDonor.objects.filter(
user=request.user, jadwal_donor__time_end__gte=timezone.now()
).order_by('jadwal_donor__time_start')
list_agenda_donor = [daftar_donor.jadwal_donor for daftar_donor in list_daftar_donor]
serializer = JadwalDonorSerializer(list_agenda_donor, many=True)
return Response(serializer.data)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)
@action(detail=False, methods=['get'])
def riwayat(self, request):
if request.user.is_authenticated:
list_daftar_donor = DaftarDonor.objects.filter(
user=request.user, has_attended=True).order_by('-jadwal_donor__time_start')
list_riwayat_donor = [daftar_donor.jadwal_donor for daftar_donor in list_daftar_donor]
page = self.paginate_queryset(list_riwayat_donor)
if page is not None:
serializer = JadwalDonorSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = JadwalDonorSerializer(list_riwayat_donor, many=True)
return Response(serializer.data)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)
return queryset
class DaftarDonorView(generics.CreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = DaftarDonorSerializer
def perform_create(self, serializer):
jadwal_donor = get_object_or_404(JadwalDonor, pk=self.kwargs.get("pk", None))
if jadwal_donor.time_end < now():
raise ValidationError("Can not register for a past event")
if DaftarDonor.objects.filter(user=self.request.user, jadwal_donor=jadwal_donor):
raise ValidationError("User has already registered")
serializer.save(user=self.request.user, jadwal_donor=jadwal_donor)
class AgendaDonorView(generics.ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = JadwalDonorSerializer
def get_queryset(self):
daftar_donors = DaftarDonor.objects.filter(user=self.request.user)
return JadwalDonor.objects.filter(time_end__gte=timezone.now(),
daftar_donors__in=daftar_donors).order_by('time_start')
class RiwayatDonorView(generics.ListAPIView):
permission_classes = [IsAuthenticated]
serializer_class = JadwalDonorSerializer
pagination_class = SmallResultsSetPagination
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')
......@@ -60,8 +60,10 @@ class Sex(models.TextChoices):
class MarriedStatus(models.TextChoices):
SINGLE = 'S', _('Single')
MARRIED = 'M', _('Married')
BELUM_KAWIN = 'BK', _('Belum Kawin')
KAWIN = 'KW', _('Kawin')
CERAI_HIDUP = 'CH', _('Cerai Hidup')
CERAI_MATI = 'CM', _('Cerai Mati')
class Profile(models.Model):
......
from rest_framework.pagination import PageNumberPagination
class SmallResultsSetPagination(PageNumberPagination):
page_size = 5
......@@ -81,7 +81,7 @@ class ProfileTests(TestCase):
sex=Sex.MALE,
profession='Vampire',
blood_type='O+',
married_status=MarriedStatus.SINGLE,
married_status=MarriedStatus.BELUM_KAWIN,
address='Jl MH Thamrin Kav 28-30 EX Lt 2 K-11, Gondangdia',
city='Jakarta Selatan',
district='Menteng',
......@@ -93,7 +93,7 @@ class ProfileTests(TestCase):
)
self.assertEqual(profile.sex, Sex.MALE)
self.assertEqual(profile.married_status, MarriedStatus.SINGLE)
self.assertEqual(profile.married_status, MarriedStatus.BELUM_KAWIN)
def test_profile_on_user_creation(self):
user = User.objects.create_user('donald@duckduckgo.org')
......
......@@ -161,6 +161,31 @@ class RegisterFullAPITestCase(APITestCase):
self.assertEqual(user.first_name, data['first_name'])
self.assertEqual(user.profile.id_card_no, data['profile']['id_card_no'])
def test_register_email_sent(self):
user = UserFactory(email='donald@duckduckgo.org', first_name='Donal')
# To dict representation
data = {
k: v
for k, v in user.__dict__.items()
if k in ('email', 'password', 'first_name')
}
profile_fields = ('body_weight', 'id_card_no', 'birthplace',
'birthdate', 'sex', 'profession', 'blood_type',
'married_status', 'address', 'city', 'district',
'village', 'phone_no', 'work_address', 'work_email',
'work_phone_no')
data['profile'] = {k: v for k, v in user.profile.__dict__.items() if k in profile_fields}
data['password'] = '5up3r_53cuer' # no passwd faker
response = self.client.post('/auth/register-full/', data=data,
format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(mail.outbox[0].to[0], data['email'])
class AccessTokenAPITestCase(APITestCase):
......
......@@ -24,4 +24,5 @@ module.exports = {
),
StaticQuery: jest.fn(),
useStaticQuery: jest.fn(),
navigate: jest.fn(),
}
......@@ -20,6 +20,8 @@ const ChangePasswordForm = ({ handleSuccess }) => {
} catch (error) {
if (error.response) {
alert(JSON.stringify(error.response.data))
} else {
alert(error.message)
}
}
}
......@@ -38,16 +40,11 @@ const ChangePasswordForm = ({ handleSuccess }) => {
placeholder="Password Lama"
ref={register}
/>
{errors.old_password && (
<Form.Control.Feedback type="invalid">
{errors.old_password.message}
</Form.Control.Feedback>
)}
</Col>
</Form.Group>
<Form.Group as={Row}>
<Form.Label column sm="4" htmlFor="new_password" className="text-red">
Password Baru<span class="text-important">*</span>
Password Baru<span className="text-important">*</span>
</Form.Label>
<Col sm="8">
<Form.Control
......@@ -71,7 +68,7 @@ const ChangePasswordForm = ({ handleSuccess }) => {
htmlFor="confirm_password"
className="text-red"
>
Konfirmasi Password<span class="text-important">*</span>
Konfirmasi Password<span className="text-important">*</span>
</Form.Label>
<Col sm="8">
<Form.Control
......
import { fireEvent, screen, waitFor } from "@testing-library/react"
import React from "react"
import "@testing-library/jest-dom"
import { fireEvent, screen } from "@testing-library/react"
import { render } from "../utils/test-util"
import { unmountComponentAtNode } from "react-dom"
import { act } from "react-dom/test-utils"
import { postUserChangePassword } from "../api"
import ChangePasswordForm from "./changepasswordform"
import { postUserChangePassword } from "../api"
let container = null
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div")
document.body.appendChild(container)
})
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container)
container.remove()
container = null
})
const fillChangePasswordForm = () => {
fireEvent.change(screen.getByLabelText(/lama/i), {
target: { value: "NotOldpass123@" },
})
window.alert = jest.fn()
const handleSuccess = jest.fn()
jest.mock("../api.js", () => ({
__esModule: true,
postUserChangePassword: jest.fn(),
}))
fireEvent.change(screen.getByLabelText(/baru/i), {
target: { value: "Newpass123@" },
})
it("submits the form and the alert shows up", async () => {
window.alert.mockClear()
fireEvent.change(screen.getByLabelText(/konfirmasi/i), {
target: { value: "Newpass123@" },
})
}
act(() => {
describe("Change Password Form", () => {
it("submits the form and the alert shows up", async () => {
window.alert = jest.fn()
const handleSuccess = jest.fn()
render(<ChangePasswordForm handleSuccess={handleSuccess} />)
fireEvent.change(screen.getByLabelText(/lama/i), {
target: { value: "Oldpass123" },
target: { value: "Oldpass123@" },
})
fireEvent.change(screen.getByLabelText(/baru/i), {
target: { value: "Newpass123" },
target: { value: "Newpass123@" },
})
fireEvent.change(screen.getByLabelText(/konfirmasi/i), {
target: { value: "Newpass123" },
target: { value: "Newpass123@" },
})
fireEvent.click(screen.getByText(/ubah/i))
await waitFor(() =>
expect(window.alert).toHaveBeenCalledWith("Password berhasil diubah.")
)
})
})
it("have different new and confirm password", async () => {
window.alert.mockClear()
act(() => {
render(<ChangePasswordForm handleSuccess={handleSuccess} />)
fireEvent.change(screen.getByLabelText(/lama/i), {
target: { value: "Oldpass123" },
})
fireEvent.change(screen.getByLabelText(/baru/i), {
target: { value: "Newpass123" },
})
fireEvent.change(screen.getByLabelText(/konfirmasi/i), {
target: { value: "Newpass124" },
})
it("shows required message on submitting empty form", async () => {
render(<ChangePasswordForm handleSuccess={jest.fn()} />)
fireEvent.click(screen.getByText(/ubah/i))
expect(await screen.findByText("Masukkan password.")).toBeInTheDocument()
expect(
screen.getByText("Masukkan konfirmasi password.")
).toBeInTheDocument()
})
})
it("show error from backend", async () => {
window.alert.mockClear()
it("show error from backend", async () => {
window.alert = jest.fn()
const handleSuccess = jest.fn()
const error = new Error("Network Error")
error.response = { data: { old_password: ["Password doesn't match"] } }
postUserChangePassword.mockRejectedValueOnce(error)
postUserChangePassword.mockImplementation(async (old_passwd, new_passwd) => {
throw new Error({
response: { data: { old_password: ["Password doesn't match"] } },
})
})
act(() => {
render(<ChangePasswordForm handleSuccess={handleSuccess} />)
fillChangePasswordForm()
fireEvent.click(screen.getByText(/ubah/i))
await waitFor(() =>
expect(window.alert).toHaveBeenCalledWith(
expect.stringMatching(/Password doesn't match/i)
)
)
})
fireEvent.change(screen.getByLabelText(/lama/i), {
target: { value: "NotOldpass123" },
})
fireEvent.change(screen.getByLabelText(/baru/i), {
target: { value: "Newpass123" },
})
fireEvent.change(screen.getByLabelText(/konfirmasi/i), {
target: { value: "Newpass123" },
})
it("shows another error", async () => {
window.alert = jest.fn()
const error = new Error("Network Error")
postUserChangePassword.mockRejectedValueOnce(error)
render(<ChangePasswordForm handleSuccess={jest.fn()} />)
fillChangePasswordForm()
fireEvent.click(screen.getByText(/ubah/i))
await waitFor(() =>
expect(window.alert).toHaveBeenCalledWith("Network Error")
)
})
})
......@@ -5,6 +5,7 @@ import { Controller } from "react-hook-form"
import PhoneInput from "react-phone-input-2"
import "react-phone-input-2/lib/style.css"
import "./user-form.css"
import { useAuth } from "../hooks/authenticate"
function CompleteProfile({
show,
......@@ -18,6 +19,7 @@ function CompleteProfile({
isLoggedIn,
name,
}) {
const { closeModalChangeProfile } = useAuth()
return (
<Modal
aria-labelledby="contained-modal-title-vcenter"
......@@ -408,11 +410,6 @@ function CompleteProfile({
type="text"
ref={register}
/>
{errors.work_address && (
<Form.Control.Feedback type="invalid">
{errors.work_address.message}
</Form.Control.Feedback>
)}
</div>
</Form.Group>
......@@ -503,11 +500,6 @@ function CompleteProfile({
name="work_phone_no"
control={control}
/>
{errors.work_phone_no && (
<Form.Control.Feedback type="invalid">
{errors.work_phone_no.message}
</Form.Control.Feedback>
)}
</div>
</Form.Group>
......@@ -516,7 +508,10 @@ function CompleteProfile({
<Button
data-testid="btn-change-paswd"
variant="btn btn-blue btn-lg shadow mt-5 "
onClick={() => navigate("/changepassword")}
onClick={() => {
closeModalChangeProfile()
navigate("/changepassword")
}}
>
Ubah Password
</Button>
......
......@@ -147,15 +147,7 @@ function FifthPage() {
)
}
export const EventDonorRegisterContext = createContext({
setPage: () => {},
onHide: () => {},
registerField: () => {},
unregisterField: () => {},
setFieldValue: () => {},
formValues: {},
onSubmit: () => {},
})
export const EventDonorRegisterContext = createContext()
export const useEventDonorRegister = () => {
const values = useContext(EventDonorRegisterContext)
......@@ -197,8 +189,8 @@ function RegistDonorEvent({
}
await postRegisterDonor(jadwalDonor.id, data)
alert("Daftar donor berhasil!")
onHide()
navigate("/profile/")
onHide()
} catch (error) {
const errorDetail = error.response
? JSON.stringify(error.response.data)
......
......@@ -5,9 +5,12 @@ import {
screen,
waitFor,
} from "@testing-library/react"
import { navigate } from "gatsby"
import React from "react"
import { postRegisterDonor } from "../api"
import EventDonorRegister from "./event-donor-register.js"
import EventDonorRegister, {
useEventDonorRegister,
} from "./event-donor-register.js"
const jadwalDonorBuilder = override => {