From c2a8000e1cdb1bf0e48b5eb411c4d113f8193192 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Wed, 19 May 2021 18:49:59 +0700 Subject: [PATCH 01/18] [CHORES] Install django-imagefield module for Image Compression --- requirements.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eb157e8..52944c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,14 +4,18 @@ boto3==1.17.12 botocore==1.20.12 cachetools==4.2.1 certifi==2020.12.5 +cffi==1.14.5 chardet==4.0.0 +colorclass==2.2.0 coverage==5.4 +cryptography==3.4.6 defusedxml==0.6.0 dj-database-url==0.5.0 Django==3.1.7 django-allauth==0.44.0 django-cors-middleware==1.5.0 django-environ==0.4.5 +django-imagefield==0.13.0 django-multiselectfield==0.1.12 django-oauth-toolkit==1.4.0 django-rest-auth==0.9.5 @@ -19,9 +23,12 @@ django-storages==1.11.1 django-utils-six==2.0 djangorestframework==3.12.2 djangorestframework-simplejwt==4.6.0 +docopt==0.6.2 +google-api-core==1.26.1 google-api-python-client==1.12.8 google-auth==1.27.0 google-auth-httplib2==0.0.4 +googleapis-common-protos==1.53.0 gunicorn==20.0.4 httplib2==0.19.0 idna==2.6 @@ -30,7 +37,10 @@ jmespath==0.10.0 lazy-object-proxy==1.5.2 mccabe==0.6.1 oauthlib==3.1.0 +packaging==20.9 Pillow==8.1.0 +pip-upgrader==1.4.15 +protobuf==3.15.6 psycopg2-binary==2.8.6 pyasn1==0.4.8 pyasn1-modules==0.2.8 @@ -39,10 +49,10 @@ PyJWT==2.0.1 pylint==2.7.0 pylint-django==2.4.2 pylint-plugin-utils==0.6 +pyparsing==2.4.7 python-dateutil==2.8.1 python-dotenv==0.15.0 python3-openid==3.2.0 -pip-upgrader==1.4.15 pytz==2021.1 requests==2.25.1 requests-oauthlib==1.3.0 @@ -53,6 +63,7 @@ six==1.15.0 social-auth-app-django==4.0.0 social-auth-core==4.0.3 sqlparse==0.4.1 +terminaltables==3.1.0 toml==0.10.2 typed-ast==1.4.2 uritemplate==3.0.1 -- GitLab From e0fed9ed788ace78036e40135bcd47ec4c7dc9b6 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Wed, 19 May 2021 19:13:38 +0700 Subject: [PATCH 02/18] [CHORES] Fix Migrations file by merge --- registrasi/migrations/0006_merge_20210519_1213.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 registrasi/migrations/0006_merge_20210519_1213.py diff --git a/registrasi/migrations/0006_merge_20210519_1213.py b/registrasi/migrations/0006_merge_20210519_1213.py new file mode 100644 index 0000000..4de0e03 --- /dev/null +++ b/registrasi/migrations/0006_merge_20210519_1213.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.7 on 2021-05-19 12:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0005_auto_20210516_1734'), + ('registrasi', '0005_bisagouser_foto'), + ] + + operations = [ + ] -- GitLab From dc0f4d38581ac59a6b8c20b32fc0ebd842e681be Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Thu, 20 May 2021 09:42:54 +0700 Subject: [PATCH 03/18] [CHORES] Remove django-image-field module --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 52944c0..71d14a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,6 @@ Django==3.1.7 django-allauth==0.44.0 django-cors-middleware==1.5.0 django-environ==0.4.5 -django-imagefield==0.13.0 django-multiselectfield==0.1.12 django-oauth-toolkit==1.4.0 django-rest-auth==0.9.5 -- GitLab From 3ed674bf09960085a12eb542994a4146ad95afea Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Thu, 20 May 2021 12:07:54 +0700 Subject: [PATCH 04/18] [REFACTOR] seek(0) of InMemoryFileUpload after creating image --- informasi_fasilitas/test_views_kegiatan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index cd84115..91cb273 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -41,6 +41,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.kegiatan_images = {'images': [image1, image2]} for image in self.kegiatan_images["images"]: FotoKegiatan.objects.create(kegiatan=self.kegiatan, foto=image) + image.seek(0) self.get_list_kegiatan_url = \ reverse('list-kegiatan', kwargs=self.kwargs_place_id) -- GitLab From 02eb18fffe3dce1a2111ece3746af72b2af8836b Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Thu, 20 May 2021 12:09:33 +0700 Subject: [PATCH 05/18] [CHORES] Create CompressedImageField (subclass ImageField) --- pplbackend/custom_model_field.py | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pplbackend/custom_model_field.py diff --git a/pplbackend/custom_model_field.py b/pplbackend/custom_model_field.py new file mode 100644 index 0000000..407ea34 --- /dev/null +++ b/pplbackend/custom_model_field.py @@ -0,0 +1,38 @@ +from io import BytesIO +import os +from django.core.files import File +from django.db import models +from django.db.models.fields.files import ImageFieldFile +from PIL import Image, ImageOps + + +class CompressedImageFieldFile(ImageFieldFile): + def save(self, name, content, save=True): + # Compressed Image + image = Image.open(content) + image = image.convert('RGB') + image = ImageOps.exif_transpose(image) + im_io = BytesIO() + image.save(im_io, "JPEG", optimize=True, quality=self.field.quality) + + # Change extension + filename = os.path.splitext(name)[0] + filename = f"{filename}.jpg" + + image = File(im_io, name=filename) + super().save(filename, image, save) + + +class CompressedImageField(models.ImageField): + attr_class = CompressedImageFieldFile + + def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, + quality=90, **kwargs): + self.quality = quality + super().__init__(verbose_name, name, width_field, height_field, **kwargs) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + if self.quality: + kwargs['quality'] = self.quality + return name, path, args, kwargs -- GitLab From d8873846bb84d4557025488b03fae6435b13f388 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Thu, 20 May 2021 12:10:39 +0700 Subject: [PATCH 06/18] [CHORES] implement CompressedImageField in FotoKegiatan models --- .../migrations/0017_auto_20210520_0336.py | 19 +++++++++++++++++++ .../migrations/0018_auto_20210520_0451.py | 19 +++++++++++++++++++ informasi_fasilitas/models.py | 4 ++-- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 informasi_fasilitas/migrations/0017_auto_20210520_0336.py create mode 100644 informasi_fasilitas/migrations/0018_auto_20210520_0451.py diff --git a/informasi_fasilitas/migrations/0017_auto_20210520_0336.py b/informasi_fasilitas/migrations/0017_auto_20210520_0336.py new file mode 100644 index 0000000..c0494ed --- /dev/null +++ b/informasi_fasilitas/migrations/0017_auto_20210520_0336.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.7 on 2021-05-20 03:36 + +from django.db import migrations +import pplbackend.custom_model_field + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0016_kegiatan_links'), + ] + + operations = [ + migrations.AlterField( + model_name='fotokegiatan', + name='foto', + field=pplbackend.custom_model_field.CompressedImageField(default=None, null=True, upload_to='kegiatan/'), + ), + ] diff --git a/informasi_fasilitas/migrations/0018_auto_20210520_0451.py b/informasi_fasilitas/migrations/0018_auto_20210520_0451.py new file mode 100644 index 0000000..a00898b --- /dev/null +++ b/informasi_fasilitas/migrations/0018_auto_20210520_0451.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.7 on 2021-05-20 04:51 + +from django.db import migrations +import pplbackend.custom_model_field + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0017_auto_20210520_0336'), + ] + + operations = [ + migrations.AlterField( + model_name='fotokegiatan', + name='foto', + field=pplbackend.custom_model_field.CompressedImageField(default=None, null=True, quality=85, upload_to='kegiatan/'), + ), + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index 7745493..788a34b 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -1,7 +1,7 @@ from django.db import models from django.contrib.auth.models import User from multiselectfield import MultiSelectField - +from pplbackend.custom_model_field import CompressedImageField from django.utils import timezone import string @@ -116,4 +116,4 @@ class KomentarKegiatan(models.Model): class FotoKegiatan(models.Model): objects = models.Manager() kegiatan = models.ForeignKey(Kegiatan, related_name="images", on_delete=models.CASCADE) - foto = models.ImageField(upload_to="kegiatan/", null=True, default=None) + foto = CompressedImageField(upload_to="kegiatan/", null=True, default=None, quality=85) -- GitLab From a6d30076218f0158039c02bc650c3870b684ec22 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Fri, 28 May 2021 10:46:40 +0700 Subject: [PATCH 07/18] [RED] Test for Implement TimeZone in kegiatan --- informasi_fasilitas/test_base.py | 87 ++++++++++-- informasi_fasilitas/test_views_kegiatan.py | 153 ++++++++++++++------- 2 files changed, 181 insertions(+), 59 deletions(-) diff --git a/informasi_fasilitas/test_base.py b/informasi_fasilitas/test_base.py index 1108940..ea50b6a 100644 --- a/informasi_fasilitas/test_base.py +++ b/informasi_fasilitas/test_base.py @@ -1,10 +1,11 @@ import json import tempfile - +import pytz from datetime import datetime, timedelta from django.test import TestCase, Client from django.contrib.auth.models import User from django.urls import reverse, path, include +from django.utils import timezone from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.base import ContentFile @@ -21,6 +22,11 @@ from .models import ( ) from pplbackend.utils import get_client_login_with_user +TIMEZONE_INDONESIA = { + "WIB": pytz.timezone("Asia/Jakarta"), + "WITA": pytz.timezone("Asia/Makassar"), + "WIT": pytz.timezone("Asia/Jayapura"), +} class InformasiFasilitasTest(TestCase): put_content_type = "application/x-www-form-urlencoded" @@ -48,34 +54,89 @@ class InformasiFasilitasTest(TestCase): 'deskripsi': 'sangat membantu', } + time_start = timezone.now() + timedelta(days=1) + time_end = timezone.now() + timedelta(days=2) + + time_start_zone = { + "WIB": time_start.astimezone(TIMEZONE_INDONESIA["WIB"]).strftime("%Y-%m-%dT%H:%M%z"), + "WITA": time_start.astimezone(TIMEZONE_INDONESIA["WITA"]).strftime("%Y-%m-%dT%H:%M%z"), + "WIT": time_start.astimezone(TIMEZONE_INDONESIA["WIT"]).strftime("%Y-%m-%dT%H:%M%z"), + } + + time_end_zone = { + "WIB": time_end.astimezone(TIMEZONE_INDONESIA["WIB"]).strftime("%Y-%m-%dT%H:%M%z"), + "WITA": time_end.astimezone(TIMEZONE_INDONESIA["WITA"]).strftime("%Y-%m-%dT%H:%M%z"), + "WIT": time_end.astimezone(TIMEZONE_INDONESIA["WIT"]).strftime("%Y-%m-%dT%H:%M%z"), + } + + # mock_kegiatan_test = { + # 'nama_kegiatan': 'mock kegiatan', + # 'penyelenggara': 'mock penyelenggara', + # 'time_start':(datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d %H:%M"), + # 'time_end': (datetime.now()+timedelta(days=2)).strftime("%Y-%m-%d %H:%M"), + # 'narahubung': 'sebuah narahubung', + # 'deskripsi': 'sebuah deskripsi', + # 'links': "www.example.com;www.example.com" + # } + mock_kegiatan_test = { 'nama_kegiatan': 'mock kegiatan', 'penyelenggara': 'mock penyelenggara', - 'time_start':(datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d %H:%M"), - 'time_end': (datetime.now()+timedelta(days=2)).strftime("%Y-%m-%d %H:%M"), - 'narahubung': 'sebuah narahubung', + "zona_waktu": "WIB", + 'time_start': time_start_zone["WIB"], + 'time_end': time_end_zone["WIB"], + 'nama_kontak': 'sebuah narahubung', + 'nomor_kontak': "1" * 15, + 'deskripsi': 'sebuah deskripsi', + 'links': "www.example.com" + } + + mock_kegiatan_wita_test = { + 'nama_kegiatan': 'mock kegiatan', + 'penyelenggara': 'mock penyelenggara', + "zona_waktu": "WITA", + 'time_start': time_start_zone["WITA"], + 'time_end': time_end_zone["WITA"], + 'nama_kontak': 'sebuah narahubung', + 'nomor_kontak': "1" * 15, + 'deskripsi': 'sebuah deskripsi', + 'links': "www.example.com" + } + + mock_kegiatan_wit_test = { + 'nama_kegiatan': 'mock kegiatan', + 'penyelenggara': 'mock penyelenggara', + "zona_waktu": "WIT", + 'time_start': time_start_zone["WIT"], + 'time_end': time_end_zone["WIT"], + 'nama_kontak': 'sebuah narahubung', + 'nomor_kontak': "1" * 15, 'deskripsi': 'sebuah deskripsi', - 'links': "www.example.com;www.example.com" + 'links': "www.example.com" } mock_kegiatan_time_order_test = { 'nama_kegiatan': 'mock kegiatan', 'penyelenggara': 'mock penyelenggara', - 'time_start':(datetime.now()+timedelta(days=2)).strftime("%Y-%m-%d %H:%M"), - 'time_end': (datetime.now()+timedelta(days=3)).strftime("%Y-%m-%d %H:%M"), - 'narahubung': 'sebuah narahubung', + "zona_waktu": "WIB", + 'time_start': time_start_zone["WIB"], + 'time_end': time_end_zone["WIB"], + 'nama_kontak': 'sebuah narahubung', + 'nomor_kontak': "1" * 15, 'deskripsi': 'sebuah deskripsi', - 'links': "www.example.com;www.example.com" + 'links': "www.example.com" } mock_kegiatan_name_order_test = { 'nama_kegiatan': 'aaaa mock kegiatan', 'penyelenggara': 'mock penyelenggara', - 'time_start':(datetime.now()+timedelta(days=2)).strftime("%Y-%m-%d %H:%M"), - 'time_end': (datetime.now()+timedelta(days=3)).strftime("%Y-%m-%d %H:%M"), - 'narahubung': 'sebuah narahubung', + "zona_waktu": "WIB", + 'time_start': time_start_zone["WIB"], + 'time_end': time_end_zone["WIB"], + 'nama_kontak': 'sebuah narahubung', + 'nomor_kontak': "1" * 15, 'deskripsi': 'sebuah deskripsi', - 'links': "www.example.com;www.example.com" + 'links': "www.example.com" } mock_komentar_kegiatan_test = { diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index d6f0daa..0004768 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -14,8 +14,8 @@ from .test_base import InformasiFasilitasViewTest from .models import Lokasi, Kegiatan, FotoKegiatan from pplbackend.utils import response_decode -class KegiatanRelatedViewTest(InformasiFasilitasViewTest): +class KegiatanRelatedViewTest(InformasiFasilitasViewTest): def setUp(self): super().setUp() self.media_root = os.path.join(settings.BASE_DIR, 'test_file/media') @@ -29,10 +29,10 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'place_id': self.lokasi.place_id, 'kegiatan_id': self.kegiatan.id, } - self.kwargs_search_kegiatan = {'query': 'mock',} - self.kwargs_search_kegiatan_fail = {'query': 'this shouldnt exist',} + self.kwargs_search_kegiatan = {'query': 'mock', } + self.kwargs_search_kegiatan_fail = {'query': 'this shouldnt exist', } - self.kwargs_kegiatan_id = {'kegiatan_id' : self.kegiatan.lokasi.place_id} + self.kwargs_kegiatan_id = {'kegiatan_id': self.kegiatan.lokasi.place_id} self.kwargs_list_kegiatan_in_order = { 'start_index': 0, 'query_limit': 10, @@ -62,11 +62,11 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.add_kegiatan_url = \ reverse('add-kegiatan', kwargs=self.kwargs_place_id) self.update_kegiatan_url = reverse('update-kegiatan', - kwargs=self.kwargs_add_or_update_kegiatan) + kwargs=self.kwargs_add_or_update_kegiatan) self.search_kegiatan_url = reverse('search-kegiatan', - kwargs=self.kwargs_search_kegiatan) + kwargs=self.kwargs_search_kegiatan) self.search_kegiatan_fail_url = reverse('search-kegiatan', - kwargs=self.kwargs_search_kegiatan_fail) + kwargs=self.kwargs_search_kegiatan_fail) self.list_foto_kegiatan_url = \ reverse('list-foto-kegiatan', kwargs=self.kwargs_get_foto_kegiatan) self.list_kegiatan_by_latest_added_url = \ @@ -88,7 +88,6 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): except OSError: pass - @override_settings(MEDIA_ROOT=("test_file" + '/media')) def test_can_add_kegiatan(self): Kegiatan.objects.all().delete() @@ -135,11 +134,13 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, - 'nama_kegiatan' : self.kegiatan.nama_kegiatan, + 'nama_kegiatan': self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, 'links': self.kegiatan.links, - 'narahubung': self.kegiatan.narahubung, + 'nama_kontak': self.kegiatan.nama_kontak, + 'nomor_kontak': self.kegiatan.nomor_kontak, + 'zona_waktu': self.kegiatan.zona_waktu, 'time_start': self.kegiatan.time_start, 'time_end': self.kegiatan.time_end, }, @@ -151,34 +152,94 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(response.status_code, HTTPStatus.OK) content = json.loads(response.content.decode('utf-8')) expected_json = { - 'id': self.kegiatan.id, - 'place_id': self.kegiatan.lokasi.place_id, - 'creator': self.kegiatan.user.last_name, - 'nama_kegiatan' : self.kegiatan.nama_kegiatan, - 'penyelenggara': self.kegiatan.penyelenggara, - 'deskripsi': self.kegiatan.deskripsi, - 'links': self.kegiatan.links, - 'narahubung': self.kegiatan.narahubung, - 'time_start': self.kegiatan.time_start, - 'time_end': self.kegiatan.time_end, + 'id': self.kegiatan.id, + 'place_id': self.kegiatan.lokasi.place_id, + 'creator': self.kegiatan.user.last_name, + 'nama_kegiatan': self.kegiatan.nama_kegiatan, + 'penyelenggara': self.kegiatan.penyelenggara, + 'deskripsi': self.kegiatan.deskripsi, + 'links': self.kegiatan.links, + 'nama_kontak': self.kegiatan.nama_kontak, + 'nomor_kontak': self.kegiatan.nomor_kontak, + 'zona_waktu': self.kegiatan.zona_waktu, + 'time_start': self.kegiatan.time_start, + 'time_end': self.kegiatan.time_end, } self.assertEqual(content, expected_json) + def test_can_get_detail_kegiatan_wita(self): + Kegiatan.objects.all().delete() + kegiatan_wita = self.create_kegiatan_test(kegiatan_dict=self.mock_kegiatan_wita_test, + user=self.user, lokasi=self.lokasi) + get_detail_kegiatan_url = \ + reverse('detail-kegiatan', kwargs={ + 'place_id': kegiatan_wita.lokasi.place_id, + 'id': kegiatan_wita.id, + }) + response = Client().get(get_detail_kegiatan_url) + self.assertEqual(response.status_code, HTTPStatus.OK) + content = json.loads(response.content.decode('utf-8')) + expected_json = { + 'id': kegiatan_wita.id, + 'place_id': kegiatan_wita.lokasi.place_id, + 'creator': kegiatan_wita.user.last_name, + 'nama_kegiatan': kegiatan_wita.nama_kegiatan, + 'penyelenggara': kegiatan_wita.penyelenggara, + 'deskripsi': kegiatan_wita.deskripsi, + 'links': kegiatan_wita.links, + 'nama_kontak': kegiatan_wita.nama_kontak, + 'nomor_kontak': kegiatan_wita.nomor_kontak, + 'zona_waktu': kegiatan_wita.zona_waktu, + 'time_start': kegiatan_wita.time_start, + 'time_end': kegiatan_wita.time_end, + } + self.assertDictEqual(content, expected_json) + + def test_can_get_detail_kegiatan_wit(self): + Kegiatan.objects.all().delete() + kegiatan_wit = self.create_kegiatan_test(kegiatan_dict=self.mock_kegiatan_wit_test, + user=self.user, lokasi=self.lokasi) + get_detail_kegiatan_url = \ + reverse('detail-kegiatan', kwargs={ + 'place_id': kegiatan_wit.lokasi.place_id, + 'id': kegiatan_wit.id, + }) + response = Client().get(get_detail_kegiatan_url) + self.assertEqual(response.status_code, HTTPStatus.OK) + content = json.loads(response.content.decode('utf-8')) + expected_json = { + 'id': kegiatan_wit.id, + 'place_id': kegiatan_wit.lokasi.place_id, + 'creator': kegiatan_wit.user.last_name, + 'nama_kegiatan': kegiatan_wit.nama_kegiatan, + 'penyelenggara': kegiatan_wit.penyelenggara, + 'deskripsi': kegiatan_wit.deskripsi, + 'links': kegiatan_wit.links, + 'nama_kontak': kegiatan_wit.nama_kontak, + 'nomor_kontak': kegiatan_wit.nomor_kontak, + 'zona_waktu': kegiatan_wit.zona_waktu, + 'time_start': kegiatan_wit.time_start, + 'time_end': kegiatan_wit.time_end, + } + self.assertDictEqual(content, expected_json) + def test_can_get_nearest_kegiatan(self): response = Client().get(self.get_nearest_kegiatan_url) self.assertEqual(response.status_code, HTTPStatus.OK) content = json.loads(response.content.decode('utf-8')) expected_json = { - 'id': self.kegiatan.id, - 'place_id': self.kegiatan.lokasi.place_id, - 'creator': self.kegiatan.user.last_name, - 'nama_kegiatan' : self.kegiatan.nama_kegiatan, - 'penyelenggara': self.kegiatan.penyelenggara, - 'deskripsi': self.kegiatan.deskripsi, - 'links': self.kegiatan.links, - 'narahubung': self.kegiatan.narahubung, - 'time_start': self.kegiatan.time_start, - 'time_end': self.kegiatan.time_end, + 'id': self.kegiatan.id, + 'place_id': self.kegiatan.lokasi.place_id, + 'creator': self.kegiatan.user.last_name, + 'nama_kegiatan': self.kegiatan.nama_kegiatan, + 'penyelenggara': self.kegiatan.penyelenggara, + 'deskripsi': self.kegiatan.deskripsi, + 'links': self.kegiatan.links, + 'nama_kontak': self.kegiatan.nama_kontak, + 'nomor_kontak': self.kegiatan.nomor_kontak, + 'zona_waktu': self.kegiatan.zona_waktu, + 'time_start': self.kegiatan.time_start, + 'time_end': self.kegiatan.time_end, } self.assertEqual(content, expected_json) @@ -212,7 +273,6 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(count, 1) self.assertEqual(count_foto, 4) - def test_update_kegiatan_lokasi_not_exist(self): Kegiatan.objects.all().delete() url = reverse('update-kegiatan', kwargs={ @@ -227,17 +287,16 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): response = self.client.put(url, data=send_data) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) - def test_can_get_list_foto_kegiatan(self): response = Client().get(self.list_foto_kegiatan_url) data = response.json() self.assertEqual(len(data), 2) counter = 1 for _, item in data.items(): - self.assertIn("test"+str(counter), item["foto"]) + self.assertIn("test" + str(counter), item["foto"]) self.assertEqual(item["kegiatan"], self.kegiatan.id) counter += 1 - + def test_can_search_kegiatan(self): response = Client().get(self.search_kegiatan_url) self.assertEqual(response.status_code, HTTPStatus.OK) @@ -247,21 +306,23 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, - 'nama_kegiatan' : self.kegiatan.nama_kegiatan, + 'nama_kegiatan': self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, 'links': self.kegiatan.links, - 'narahubung': self.kegiatan.narahubung, + 'nama_kontak': self.kegiatan.nama_kontak, + 'nomor_kontak': self.kegiatan.nomor_kontak, + 'zona_waktu': self.kegiatan.zona_waktu, 'time_start': self.kegiatan.time_start, 'time_end': self.kegiatan.time_end, }, } self.assertEqual(content, expected_json) - + def test_search_kegiatan_empty_result_or_fail(self): response = Client().get(self.search_kegiatan_fail_url) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) - + def test_can_get_list_kegiatan_by_name_order(self): Kegiatan.objects.all().delete() response_params = self.mock_kegiatan_test.copy() @@ -283,12 +344,12 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): response_id_in_order = list(content.keys()) self.assertEqual(content[base_mock_id]['nama_kegiatan'], - self.kegiatan.nama_kegiatan) + self.kegiatan.nama_kegiatan) self.assertEqual(content[ordered_name_mock_id]['nama_kegiatan'], - self.mock_kegiatan_name_order_test['nama_kegiatan']) + self.mock_kegiatan_name_order_test['nama_kegiatan']) self.assertEqual(response_id_in_order[0], ordered_name_mock_id) self.assertEqual(response_id_in_order[1], base_mock_id) - + def test_fail_get_list_kegiatan_by_name_order(self): response = Client().get(self.list_kegiatan_by_name_fail_url) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) @@ -314,16 +375,16 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): response_id_in_order = list(content.keys()) self.assertEqual(content[base_mock_id]['time_start'], - self.kegiatan.time_start) + self.kegiatan.time_start) self.assertEqual(content[ordered_time_mock_id]['time_start'], - self.mock_kegiatan_time_order_test['time_start']) + self.mock_kegiatan_time_order_test['time_start']) self.assertEqual(response_id_in_order[0], base_mock_id) self.assertEqual(response_id_in_order[1], ordered_time_mock_id) - + def test_fail_get_list_kegiatan_by_time_order(self): response = Client().get(self.list_kegiatan_by_time_fail_url) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) - + def test_can_get_list_kegiatan_by_latest_added(self): Kegiatan.objects.all().delete() response_params = self.mock_kegiatan_test.copy() @@ -342,7 +403,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): response_id_in_order = list(content.keys()) self.assertGreater(int(response_id_in_order[0]), int(response_id_in_order[1])) - + def test_fail_get_list_kegiatan_by_latest_added(self): response = Client().get(self.list_kegiatan_by_latest_added_fail_url) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) -- GitLab From 30b0211233fcd0281df12c732fb97c7455d37f8d Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Fri, 28 May 2021 10:48:25 +0700 Subject: [PATCH 08/18] [CHORES] Merge Registrasi migrations --- registrasi/migrations/0008_merge_20210527_2250.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 registrasi/migrations/0008_merge_20210527_2250.py diff --git a/registrasi/migrations/0008_merge_20210527_2250.py b/registrasi/migrations/0008_merge_20210527_2250.py new file mode 100644 index 0000000..9730e9c --- /dev/null +++ b/registrasi/migrations/0008_merge_20210527_2250.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.7 on 2021-05-27 22:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0007_merge_20210517_0447'), + ('registrasi', '0006_merge_20210519_1213'), + ] + + operations = [ + ] -- GitLab From 6c41b7f0d44164851c7dce873cbac992caf4bc37 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Fri, 28 May 2021 10:50:07 +0700 Subject: [PATCH 09/18] [CHORES] delete narahubung and add fields nomor_kontak, nama_kontak, zona_waktu at Kegiatan models --- .../migrations/0019_auto_20210527_2251.py | 32 +++++++++++++++++++ informasi_fasilitas/models.py | 10 +++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 informasi_fasilitas/migrations/0019_auto_20210527_2251.py diff --git a/informasi_fasilitas/migrations/0019_auto_20210527_2251.py b/informasi_fasilitas/migrations/0019_auto_20210527_2251.py new file mode 100644 index 0000000..b325187 --- /dev/null +++ b/informasi_fasilitas/migrations/0019_auto_20210527_2251.py @@ -0,0 +1,32 @@ +# Generated by Django 3.1.7 on 2021-05-27 22:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0018_auto_20210520_0451'), + ] + + operations = [ + migrations.RemoveField( + model_name='kegiatan', + name='narahubung', + ), + migrations.AddField( + model_name='kegiatan', + name='nama_kontak', + field=models.CharField(blank=True, max_length=50, null=True), + ), + migrations.AddField( + model_name='kegiatan', + name='nomor_kontak', + field=models.CharField(default='111111111111111', max_length=15), + ), + migrations.AddField( + model_name='kegiatan', + name='zona_waktu', + field=models.CharField(choices=[('WIB', 'Disabilitas Fisik'), ('WITA', 'Disabilitas Mental'), ('WIT', 'Disabilitas Intelektual')], default='WIB', max_length=4), + ), + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index 9fc2c04..e313fd0 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -45,6 +45,12 @@ JENIS_DISABILITAS = ( ('DS', 'Disabilitas Sensorik'), ) +TIMEZONE_INDONESIA = ( + ('WIB', 'Disabilitas Fisik'), + ('WITA', 'Disabilitas Mental'), + ('WIT', 'Disabilitas Intelektual'), +) + def _default_lokasi_place_id(): return \ ''.join([random.choice(string.ascii_letters) for _ in range(40)]) @@ -80,9 +86,11 @@ class Kegiatan(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) nama_kegiatan = models.CharField(max_length=50) penyelenggara = models.CharField(max_length=50) - narahubung = models.TextField(null=True) + nama_kontak = models.CharField(max_length=50, null=True, blank=True) + nomor_kontak = models.CharField(max_length=15, unique=False, null=False, default="1"*15) deskripsi = models.TextField(null=False) links = models.TextField(null=True, blank=True) + zona_waktu = models.CharField(choices=TIMEZONE_INDONESIA, null=False, max_length=4, default="WIB") time_start = models.DateTimeField(blank=False, null=False, default=timezone.now) time_end = models.DateTimeField(blank=True, null=True) -- GitLab From 54f9eecb1c12e8bcb7a04118f82671255b5bd356 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Fri, 28 May 2021 10:54:06 +0700 Subject: [PATCH 10/18] [CHORES] Change DateTime format for rest framework serializer --- pplbackend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pplbackend/settings.py b/pplbackend/settings.py index cb98bad..47d3d2c 100644 --- a/pplbackend/settings.py +++ b/pplbackend/settings.py @@ -202,7 +202,7 @@ SOCIALACCOUNT_PROVIDERS = { LOGIN_REDIRECT_URL = '/' REST_FRAMEWORK = { - 'DATETIME_FORMAT': "%Y-%m-%d %H:%M", + 'DATETIME_FORMAT': "%Y-%m-%dT%H:%M%z", 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, 'DEFAULT_AUTHENTICATION_CLASSES': [ -- GitLab From 267176eb0e122a6827634cb4c5e8decc71c42f87 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Fri, 28 May 2021 10:55:51 +0700 Subject: [PATCH 11/18] [GREEN] Implement timezone in Kegiatan model related api --- informasi_fasilitas/serializers.py | 41 +++++++++++++++++++++++++-- informasi_fasilitas/views_kegiatan.py | 18 ++++++------ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 8b034ae..91e5219 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -1,6 +1,21 @@ from rest_framework import serializers from .models import Lokasi, Kegiatan, KomentarKegiatan, FotoKegiatan +import pytz + +TIMEZONE_INDONESIA = { + "WIB": pytz.timezone("Asia/Jakarta"), + "WITA": pytz.timezone("Asia/Makassar"), + "WIT": pytz.timezone("Asia/Jayapura"), +} + + +def time_converter(time, zona_waktu): + if zona_waktu in TIMEZONE_INDONESIA: + time = time.astimezone(TIMEZONE_INDONESIA[zona_waktu]) + else: + time = time.astimezone("Asia/Jakarta") + return time class LokasiSerializer(serializers.ModelSerializer): @@ -37,16 +52,38 @@ class KegiatanSerializer(serializers.ModelSerializer): model = Kegiatan fields = ('id', 'place_id', 'creator', 'lokasi', 'user', 'nama_kegiatan', 'penyelenggara', 'deskripsi', - "links","narahubung", 'time_start', 'time_end') + "links", "nomor_kontak", "nama_kontak", + "zona_waktu", 'time_start', 'time_end') + extra_kwargs = { 'nama_kegiatan': {'required': True}, 'penyelenggara': {'required': True}, 'deskripsi': {'required': True}, - 'narahubung': {"required": True}, + 'nomor_kontak': {"required": True}, + 'zona_waktu': {"required": True}, 'time_start': {'required': True}, } +class KegiatanSerializerRead(KegiatanSerializer): + time_start = serializers.SerializerMethodField() + time_end = serializers.SerializerMethodField() + + def get_time_start(self, obj): + time = obj.time_start + zona_waktu = obj.zona_waktu + time = time_converter(time, zona_waktu) + return time.strftime('%Y-%m-%dT%H:%M%z') + + def get_time_end(self, obj): + time = obj.time_end + zona_waktu = obj.zona_waktu + if time is None: + return None + time = time_converter(time, zona_waktu) + return time.strftime('%Y-%m-%dT%H:%M%z') + + class KomentarKegiatanSerializer(serializers.ModelSerializer): kegiatan = serializers.IntegerField(source='kegiatan.id', read_only=True) creator = serializers.CharField(source='user.last_name', read_only=True) diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 0c8094c..4a165b0 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -9,7 +9,7 @@ from rest_framework.views import APIView from rest_framework.exceptions import AuthenticationFailed, NotFound from .models import Kegiatan, Lokasi, FotoKegiatan from django.contrib.auth.models import User -from .serializers import KegiatanSerializer, FotoKegiatanSerializer +from .serializers import KegiatanSerializer, KegiatanSerializerRead, FotoKegiatanSerializer @api_view(['GET']) @@ -17,7 +17,7 @@ from .serializers import KegiatanSerializer, FotoKegiatanSerializer @permission_classes([]) def list_kegiatan(request, place_id): queryset = Kegiatan.objects.filter(lokasi__place_id=place_id) - serializer = KegiatanSerializer(queryset, many=True) + serializer = KegiatanSerializerRead(queryset, many=True) data_response = serializer.data new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in data_response} return JsonResponse(new_dict, status=HTTPStatus.OK) @@ -29,7 +29,7 @@ def list_kegiatan(request, place_id): def detail_kegiatan(request, place_id, id): try: queryset = Kegiatan.objects.get(lokasi__place_id=place_id, id=id) - serializer = KegiatanSerializer(queryset, many=False) + serializer = KegiatanSerializerRead(queryset, many=False) return JsonResponse(_clean_json_kegiatan(serializer.data), status=HTTPStatus.OK) except Kegiatan.DoesNotExist: raise NotFound(detail="Kegiatan doesn't exist") @@ -43,7 +43,7 @@ def nearest_kegiatan(request): queryset = Kegiatan.objects.filter(time_start__gte=time_now).order_by('time_start').first() if queryset is None: raise NotFound(detail="No Kegiatan available") - serializer = KegiatanSerializer(queryset, many=False) + serializer = KegiatanSerializerRead(queryset, many=False) return JsonResponse(_clean_json_kegiatan(serializer.data), status=HTTPStatus.OK) @api_view(['POST']) @@ -105,7 +105,7 @@ def search_kegiatan(request, query): queryset = query_by_nama | query_by_deskripsi | query_by_penyelenggara if not queryset.exists(): raise NotFound(detail="No Kegiatan available") - serializer = KegiatanSerializer(queryset, many=True) + serializer = KegiatanSerializerRead(queryset, many=True) new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} return JsonResponse(new_dict, status=HTTPStatus.OK) @@ -118,7 +118,7 @@ def list_kegiatan_by_name(request, start_index, query_limit=9): queryset = queryset[start_index:start_index+query_limit] if not queryset.exists(): raise NotFound(detail="No Kegiatan available") - serializer = KegiatanSerializer(queryset, many=True) + serializer = KegiatanSerializerRead(queryset, many=True) new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} return JsonResponse(new_dict, status=HTTPStatus.OK) @@ -131,7 +131,7 @@ def list_kegiatan_by_time(request, start_index, query_limit=9): queryset = queryset[start_index:start_index+query_limit] if not queryset.exists(): raise NotFound(detail="No Kegiatan available") - serializer = KegiatanSerializer(queryset, many=True) + serializer = KegiatanSerializerRead(queryset, many=True) new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} return JsonResponse(new_dict, status=HTTPStatus.OK) @@ -144,7 +144,7 @@ def list_kegiatan_by_latest_added(request, start_index, query_limit=9): queryset = queryset[start_index:start_index+query_limit] if not queryset.exists(): raise NotFound(detail="No Kegiatan available") - serializer = KegiatanSerializer(queryset, many=True) + serializer = KegiatanSerializerRead(queryset, many=True) new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} return JsonResponse(new_dict, status=HTTPStatus.OK) @@ -157,7 +157,7 @@ def _clean_json_kegiatan(kegiatan): def _add_foto_kegiatan(kegiatan, list_image): _create_list_kegiatan_foto(kegiatan, list_image) kegiatan.refresh_from_db() - return KegiatanSerializer(kegiatan) + return KegiatanSerializerRead(kegiatan) def _create_list_kegiatan_foto(kegiatan, list_image): -- GitLab From 1a7cb8e5580413a8e371524dd1dadcb8e1afa112 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 10:05:21 +0700 Subject: [PATCH 12/18] [REFACTOR] FIX zona_waktu choices --- informasi_fasilitas/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index e313fd0..ae59d6a 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -46,9 +46,9 @@ JENIS_DISABILITAS = ( ) TIMEZONE_INDONESIA = ( - ('WIB', 'Disabilitas Fisik'), - ('WITA', 'Disabilitas Mental'), - ('WIT', 'Disabilitas Intelektual'), + ('WIB', 'WIB'), + ('WITA', 'WITA'), + ('WIT', 'WIT'), ) def _default_lokasi_place_id(): -- GitLab From 13a684cda4863be8bf13dc3e1a2668d7b4ca6992 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 19:56:05 +0700 Subject: [PATCH 13/18] Pbi 12 info kegiatan --- informasi_fasilitas/serializers.py | 4 +-- informasi_fasilitas/test_views_kegiatan.py | 41 ++++++++++++---------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 91e5219..9a15b98 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -73,7 +73,7 @@ class KegiatanSerializerRead(KegiatanSerializer): time = obj.time_start zona_waktu = obj.zona_waktu time = time_converter(time, zona_waktu) - return time.strftime('%Y-%m-%dT%H:%M%z') + return time.strftime('%Y-%m-%dT%H:%M') def get_time_end(self, obj): time = obj.time_end @@ -81,7 +81,7 @@ class KegiatanSerializerRead(KegiatanSerializer): if time is None: return None time = time_converter(time, zona_waktu) - return time.strftime('%Y-%m-%dT%H:%M%z') + return time.strftime('%Y-%m-%dT%H:%M') class KomentarKegiatanSerializer(serializers.ModelSerializer): diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index 0004768..ba72359 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -20,6 +20,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): super().setUp() self.media_root = os.path.join(settings.BASE_DIR, 'test_file/media') self.kegiatan = self.create_kegiatan_test(self.user, self.lokasi) + self.kegiatan_time_start = self.kegiatan.time_start[:16] + self.kegiatan_time_end = self.kegiatan.time_end[:16] self.kwargs_place_id = {'place_id': self.lokasi.place_id} self.kwargs_add_or_update_kegiatan = { 'place_id': self.lokasi.place_id, @@ -99,8 +101,9 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): data.pop("id", None) expected_json = self.mock_kegiatan_test.copy() expected_json.update({'creator': 'mock last_name', - 'place_id': 'mock_place_id' - }) + 'place_id': 'mock_place_id', + 'time_start': self.kegiatan_time_start, + 'time_end': self.kegiatan_time_end}) self.assertEqual(response.status_code, HTTPStatus.CREATED) self.assertDictEqual(data, expected_json) count = Kegiatan.objects.all().count() @@ -141,8 +144,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'nama_kontak': self.kegiatan.nama_kontak, 'nomor_kontak': self.kegiatan.nomor_kontak, 'zona_waktu': self.kegiatan.zona_waktu, - 'time_start': self.kegiatan.time_start, - 'time_end': self.kegiatan.time_end, + 'time_start': self.kegiatan_time_start, + 'time_end': self.kegiatan_time_end, }, } self.assertEqual(content, expected_json) @@ -162,8 +165,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'nama_kontak': self.kegiatan.nama_kontak, 'nomor_kontak': self.kegiatan.nomor_kontak, 'zona_waktu': self.kegiatan.zona_waktu, - 'time_start': self.kegiatan.time_start, - 'time_end': self.kegiatan.time_end, + 'time_start': self.kegiatan_time_start, + 'time_end': self.kegiatan_time_end, } self.assertEqual(content, expected_json) @@ -190,8 +193,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'nama_kontak': kegiatan_wita.nama_kontak, 'nomor_kontak': kegiatan_wita.nomor_kontak, 'zona_waktu': kegiatan_wita.zona_waktu, - 'time_start': kegiatan_wita.time_start, - 'time_end': kegiatan_wita.time_end, + 'time_start': kegiatan_wita.time_start[:16], + 'time_end': kegiatan_wita.time_end[:16], } self.assertDictEqual(content, expected_json) @@ -218,8 +221,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'nama_kontak': kegiatan_wit.nama_kontak, 'nomor_kontak': kegiatan_wit.nomor_kontak, 'zona_waktu': kegiatan_wit.zona_waktu, - 'time_start': kegiatan_wit.time_start, - 'time_end': kegiatan_wit.time_end, + 'time_start': kegiatan_wit.time_start[:16], + 'time_end': kegiatan_wit.time_end[:16], } self.assertDictEqual(content, expected_json) @@ -238,8 +241,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'nama_kontak': self.kegiatan.nama_kontak, 'nomor_kontak': self.kegiatan.nomor_kontak, 'zona_waktu': self.kegiatan.zona_waktu, - 'time_start': self.kegiatan.time_start, - 'time_end': self.kegiatan.time_end, + 'time_start': self.kegiatan_time_start, + 'time_end': self.kegiatan_time_end, } self.assertEqual(content, expected_json) @@ -261,8 +264,10 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): data.pop("id", None) expected_json = self.mock_kegiatan_test.copy() expected_json.update({'creator': 'mock last_name', - 'place_id': 'mock_place_id' - }) + 'place_id': 'mock_place_id', + 'time_start': self.kegiatan_time_start, + 'time_end': self.kegiatan_time_end}) + send_data.pop("images") expected_json.update(send_data) self.assertEqual(response.status_code, HTTPStatus.ACCEPTED) @@ -313,8 +318,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'nama_kontak': self.kegiatan.nama_kontak, 'nomor_kontak': self.kegiatan.nomor_kontak, 'zona_waktu': self.kegiatan.zona_waktu, - 'time_start': self.kegiatan.time_start, - 'time_end': self.kegiatan.time_end, + 'time_start': self.kegiatan_time_start, + 'time_end': self.kegiatan_time_end, }, } self.assertEqual(content, expected_json) @@ -375,9 +380,9 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): response_id_in_order = list(content.keys()) self.assertEqual(content[base_mock_id]['time_start'], - self.kegiatan.time_start) + self.kegiatan_time_start) self.assertEqual(content[ordered_time_mock_id]['time_start'], - self.mock_kegiatan_time_order_test['time_start']) + self.mock_kegiatan_time_order_test['time_start'][:16]) self.assertEqual(response_id_in_order[0], base_mock_id) self.assertEqual(response_id_in_order[1], ordered_time_mock_id) -- GitLab From 799b08a6909fb6d66768faffae79f6ba6e61811b Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 21:43:28 +0700 Subject: [PATCH 14/18] [RED] Change date format to YYYY-MM-DD HH:mm --- informasi_fasilitas/test_base.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/informasi_fasilitas/test_base.py b/informasi_fasilitas/test_base.py index ea50b6a..52f1065 100644 --- a/informasi_fasilitas/test_base.py +++ b/informasi_fasilitas/test_base.py @@ -58,15 +58,15 @@ class InformasiFasilitasTest(TestCase): time_end = timezone.now() + timedelta(days=2) time_start_zone = { - "WIB": time_start.astimezone(TIMEZONE_INDONESIA["WIB"]).strftime("%Y-%m-%dT%H:%M%z"), - "WITA": time_start.astimezone(TIMEZONE_INDONESIA["WITA"]).strftime("%Y-%m-%dT%H:%M%z"), - "WIT": time_start.astimezone(TIMEZONE_INDONESIA["WIT"]).strftime("%Y-%m-%dT%H:%M%z"), + "WIB": time_start.astimezone(TIMEZONE_INDONESIA["WIB"]).strftime("%Y-%m-%d %H:%M%z"), + "WITA": time_start.astimezone(TIMEZONE_INDONESIA["WITA"]).strftime("%Y-%m-%d %H:%M%z"), + "WIT": time_start.astimezone(TIMEZONE_INDONESIA["WIT"]).strftime("%Y-%m-%d %H:%M%z"), } time_end_zone = { - "WIB": time_end.astimezone(TIMEZONE_INDONESIA["WIB"]).strftime("%Y-%m-%dT%H:%M%z"), - "WITA": time_end.astimezone(TIMEZONE_INDONESIA["WITA"]).strftime("%Y-%m-%dT%H:%M%z"), - "WIT": time_end.astimezone(TIMEZONE_INDONESIA["WIT"]).strftime("%Y-%m-%dT%H:%M%z"), + "WIB": time_end.astimezone(TIMEZONE_INDONESIA["WIB"]).strftime("%Y-%m-%d %H:%M%z"), + "WITA": time_end.astimezone(TIMEZONE_INDONESIA["WITA"]).strftime("%Y-%m-%d %H:%M%z"), + "WIT": time_end.astimezone(TIMEZONE_INDONESIA["WIT"]).strftime("%Y-%m-%d %H:%M%z"), } # mock_kegiatan_test = { -- GitLab From 214c1b96753f9d9011e1b7f9b12e01e29e51f9c8 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 21:44:09 +0700 Subject: [PATCH 15/18] [GREEN] Implement Change date format --- informasi_fasilitas/serializers.py | 4 ++-- pplbackend/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index aadba86..c73c53e 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -73,7 +73,7 @@ class KegiatanSerializerRead(KegiatanSerializer): time = obj.time_start zona_waktu = obj.zona_waktu time = time_converter(time, zona_waktu) - return time.strftime('%Y-%m-%dT%H:%M') + return time.strftime('%Y-%m-%d %H:%M') def get_time_end(self, obj): time = obj.time_end @@ -81,7 +81,7 @@ class KegiatanSerializerRead(KegiatanSerializer): if time is None: return None time = time_converter(time, zona_waktu) - return time.strftime('%Y-%m-%dT%H:%M') + return time.strftime('%Y-%m-%d %H:%M') class KomentarKegiatanSerializer(serializers.ModelSerializer): kegiatan = serializers.IntegerField(source='kegiatan.id', read_only=True) diff --git a/pplbackend/settings.py b/pplbackend/settings.py index 47d3d2c..20beac1 100644 --- a/pplbackend/settings.py +++ b/pplbackend/settings.py @@ -202,7 +202,7 @@ SOCIALACCOUNT_PROVIDERS = { LOGIN_REDIRECT_URL = '/' REST_FRAMEWORK = { - 'DATETIME_FORMAT': "%Y-%m-%dT%H:%M%z", + 'DATETIME_FORMAT': "%Y-%m-%d %H:%M%z", 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, 'DEFAULT_AUTHENTICATION_CLASSES': [ -- GitLab From d55fc4d8fa8506d575e93baec298741b7cf6d926 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 09:52:54 +0700 Subject: [PATCH 16/18] [REFACTOR] Define fields for FotoKegiatanSerializer --- informasi_fasilitas/serializers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index c73c53e..0c4e50e 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -34,10 +34,11 @@ class LokasiSerializer(serializers.ModelSerializer): class FotoKegiatanSerializer(serializers.ModelSerializer): - place_id = serializers.CharField(source='lokasi.place_id', read_only=True) + place_id = serializers.CharField(source='kegiatan.lokasi.place_id', read_only=True) + class Meta: model = FotoKegiatan - fields = '__all__' + fields = ("id", "kegiatan", "place_id", "foto") extra_kwargs = { 'foto': {'required': True}, 'kegiatan': {'required': True}, -- GitLab From 2dee9919be51990c99df50ed11cfb21bccb91694 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 09:54:10 +0700 Subject: [PATCH 17/18] [RED] Add test for add(single) and update FotoKegiatan --- informasi_fasilitas/test_views_kegiatan.py | 56 +++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index ba72359..c9135b7 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -31,6 +31,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'place_id': self.lokasi.place_id, 'kegiatan_id': self.kegiatan.id, } + self.kwargs_search_kegiatan = {'query': 'mock', } self.kwargs_search_kegiatan_fail = {'query': 'this shouldnt exist', } @@ -51,26 +52,43 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): content_type='image/jpeg') self.kegiatan_images = {'images': [image1, image2]} + self.foto = None for image in self.kegiatan_images["images"]: - FotoKegiatan.objects.create(kegiatan=self.kegiatan, foto=image) + foto = FotoKegiatan.objects.create(kegiatan=self.kegiatan, foto=image) + self.foto = foto image.seek(0) + self.kwargs_update_foto_kegiatan = { + 'place_id': self.lokasi.place_id, + 'kegiatan_id': self.kegiatan.id, + 'id': self.foto.id, + } + self.get_list_kegiatan_url = \ reverse('list-kegiatan', kwargs=self.kwargs_place_id) self.get_detail_kegiatan_url = \ reverse('detail-kegiatan', kwargs=self.kwargs_add_or_update_kegiatan) self.get_nearest_kegiatan_url = \ reverse('nearest-kegiatan') + self.add_kegiatan_url = \ reverse('add-kegiatan', kwargs=self.kwargs_place_id) self.update_kegiatan_url = reverse('update-kegiatan', kwargs=self.kwargs_add_or_update_kegiatan) + + self.search_kegiatan_url = reverse('search-kegiatan', kwargs=self.kwargs_search_kegiatan) self.search_kegiatan_fail_url = reverse('search-kegiatan', kwargs=self.kwargs_search_kegiatan_fail) + self.list_foto_kegiatan_url = \ reverse('list-foto-kegiatan', kwargs=self.kwargs_get_foto_kegiatan) + self.add_foto_kegiatan_url = \ + reverse('add-foto-kegiatan', kwargs=self.kwargs_get_foto_kegiatan) + self.update_foto_kegiatan_url = \ + reverse('update-foto-kegiatan', kwargs=self.kwargs_update_foto_kegiatan) + self.list_kegiatan_by_latest_added_url = \ reverse('list-kegiatan-by-latest-added', kwargs=self.kwargs_list_kegiatan_in_order) self.list_kegiatan_by_name_url = \ @@ -302,6 +320,42 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(item["kegiatan"], self.kegiatan.id) counter += 1 + def test_can_add_foto_kegiatan(self): + data = {"foto": self.kegiatan_images["images"][0]} + response = self.client.post(self.add_foto_kegiatan_url, data=data) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + kegiatan_fotos = FotoKegiatan.objects.all() + self.assertEqual(len(kegiatan_fotos), 3) + + def test_failed_add_foto_kegiatan(self): + data = {"foto": self.kegiatan_images["images"][0]} + url = reverse('add-foto-kegiatan', kwargs={ + 'place_id': "IDSNKCM", + 'kegiatan_id': 222, + }) + response = self.client.post(url, data=data) + self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) + kegiatan_fotos = FotoKegiatan.objects.all() + self.assertEqual(len(kegiatan_fotos), 2) + + def test_can_put_update_foto_kegiatan(self): + data = {"foto": self.kegiatan_images["images"][0]} + response = self.client.put(self.update_foto_kegiatan_url, data=data) + self.assertEqual(response.status_code, HTTPStatus.ACCEPTED) + output = response.json() + self.assertIsNotNone(output.get("foto")) + self.assertNotEqual(self.foto.foto, output.get("foto")) + + def test_failed_put_update_foto_kegiatan(self): + data = {"foto": self.kegiatan_images["images"][0]} + url = reverse('update-foto-kegiatan', kwargs={ + 'place_id': "IDSNKCM", + 'kegiatan_id': 222, + 'id': 444, + }) + response = self.client.put(url, data=data) + self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) + def test_can_search_kegiatan(self): response = Client().get(self.search_kegiatan_url) self.assertEqual(response.status_code, HTTPStatus.OK) -- GitLab From 9c03bec9371ae103ab4fae02b118110eca80de7c Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 09:55:02 +0700 Subject: [PATCH 18/18] [GREEN] Implement Add and Update Foto Kegiatan --- informasi_fasilitas/urls.py | 6 +++++ informasi_fasilitas/views_kegiatan.py | 34 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 05f598d..91b1adc 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -50,6 +50,12 @@ urlpatterns = [ path('lokasi/list-foto-kegiatan//', views_kegiatan.list_foto_kegiatan, name='list-foto-kegiatan'), + + path('lokasi/add-foto-kegiatan//', + views_kegiatan.add_foto_kegiatan, name='add-foto-kegiatan'), + + path('lokasi/update-foto-kegiatan///', + views_kegiatan.update_foto_kegiatan, name='update-foto-kegiatan'), path('lokasi/list-kegiatan-by-latest-added//', views_kegiatan.list_kegiatan_by_latest_added, name='list-kegiatan-by-latest-added'), diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 4a165b0..fb938e0 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -95,6 +95,40 @@ def list_foto_kegiatan(request, place_id, kegiatan_id): new_dict = {item['id']: dict(item) for item in data_response} return JsonResponse(new_dict, status=HTTPStatus.OK) + +@api_view(['POST']) +@authentication_classes([TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def add_foto_kegiatan(request, place_id, kegiatan_id): + try: + kegiatan = Kegiatan.objects.get(lokasi__place_id=place_id, id=kegiatan_id) + data = request.data.dict() + data.update({"kegiatan": kegiatan.id}) + + serializer = FotoKegiatanSerializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + + return JsonResponse(serializer.data, status=HTTPStatus.CREATED) + except Kegiatan.DoesNotExist: + raise NotFound(detail="Kegiatan doesn't exist") + + +@api_view(['PUT']) +@authentication_classes([TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def update_foto_kegiatan(request, place_id, kegiatan_id, id): + try: + foto_kegiatan = FotoKegiatan.objects.get(kegiatan__lokasi__place_id=place_id, + kegiatan_id=kegiatan_id, + id=id) + serializer = FotoKegiatanSerializer(foto_kegiatan, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + return JsonResponse(serializer.data, status=HTTPStatus.ACCEPTED) + except FotoKegiatan.DoesNotExist: + raise NotFound(detail="Foto Kegiatan doesn't exist") + + @api_view(['GET']) @authentication_classes([]) @permission_classes([]) -- GitLab