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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] [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/25] 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/25] [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/25] [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/25] [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/25] [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/25] [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 From a9db0dcc9daf4d7fe0ed93cce190f554d3ed3280 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 11:49:39 +0700 Subject: [PATCH 19/25] [REFACTOR] Change params do PUT request function --- informasi_fasilitas/test_views_fasilitas.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/informasi_fasilitas/test_views_fasilitas.py b/informasi_fasilitas/test_views_fasilitas.py index cbd8f6f..0a589ca 100644 --- a/informasi_fasilitas/test_views_fasilitas.py +++ b/informasi_fasilitas/test_views_fasilitas.py @@ -170,6 +170,5 @@ class FasilitasRelatedViewTest(InformasiFasilitasViewTest): 'rating': 3, 'tag': 'KR', } - response = self.client.put(self.update_fasilitas_url, data=send_data, - content_type=self.put_content_type) + response = self.client.put(self.update_fasilitas_url, data=send_data) self.assertEqual(response.status_code, HTTPStatus.ACCEPTED) -- GitLab From b2450ca3adf8a88cce130da8e8ba38582dc753dc Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 11:55:54 +0700 Subject: [PATCH 20/25] [REFACTOR] Refactor on Fasilitas List, Detail, and Update --- informasi_fasilitas/serializers.py | 55 ++++++++++++++++++- informasi_fasilitas/views.py | 84 +++++------------------------- 2 files changed, 66 insertions(+), 73 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 0c4e50e..5fa260f 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from .models import Lokasi, Kegiatan, KomentarKegiatan, FotoKegiatan +from .models import Lokasi, Fasilitas, Kegiatan, KomentarKegiatan, FotoKegiatan import pytz TIMEZONE_INDONESIA = { @@ -33,6 +33,59 @@ class LokasiSerializer(serializers.ModelSerializer): } +class FasilitasSerializer(serializers.ModelSerializer): + class Meta: + model = Fasilitas + fields = ("id", "date_time", "deskripsi", "like", + "dislike", "rating", "tag", "disabilitas", + "jumlah", "image", "is_verified") + + +class FasilitasSerializerWrite(FasilitasSerializer): + class Meta: + model = FasilitasSerializer.Meta.model + fields = FasilitasSerializer.Meta.fields + ('lokasi', 'user') + extra_kwargs = { + 'lokasi': {'required': True}, + 'user': {'required': True}, + 'tag': {"required": True}, + 'deskripsi':{"required": True}, + 'rating': {"required": True}, + 'disabilitas': {"required": True}, + 'jumlah': {"required": True}, + } + + +class FasilitasSerializerRead(FasilitasSerializer): + place_id = serializers.CharField(source='lokasi.place_id', read_only=True) + creator = serializers.CharField(source='user.last_name', read_only=True) + creator_email = serializers.CharField(source='user.email', read_only=True) + date_time = serializers.SerializerMethodField() + image = serializers.SerializerMethodField() + like = serializers.SerializerMethodField() + dislike = serializers.SerializerMethodField() + + def get_date_time(self, obj): + time = obj.date_time + return time.strftime("%d-%m-%Y %H:%M:%S") + + def get_image(self, obj): + image = obj.image + return '/media/' + str(image) + + def get_like(self, obj): + likes = obj.likes_set.count() + return likes + + def get_dislike(self, obj): + dislike = obj.dislikes_set.count() + return dislike + + class Meta: + model = FasilitasSerializer.Meta.model + fields = FasilitasSerializer.Meta.fields + ('place_id', 'creator', "creator_email") + + class FotoKegiatanSerializer(serializers.ModelSerializer): place_id = serializers.CharField(source='kegiatan.lokasi.place_id', read_only=True) diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index a52ef9c..fe14ecd 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -8,7 +8,7 @@ from rest_framework.response import Response from rest_framework import viewsets from notification.utils import send_komentar_notification -from .serializers import LokasiSerializer +from .serializers import LokasiSerializer, FasilitasSerializerRead, FasilitasSerializerWrite from .models import Lokasi, Fasilitas, Komentar, Likes, Dislikes from .permissions import UserPermission @@ -56,31 +56,10 @@ def list_fasilitas(request, place_id): try: lokasi = Lokasi.objects.get(place_id=place_id) list_fasilitas = Fasilitas.objects.filter(lokasi=lokasi) - return_json = {} - for fasilitas in list_fasilitas: - user = fasilitas.user - fasilitas.like = Likes.objects.filter( - fasilitas=fasilitas).count() - fasilitas.dislike = Dislikes.objects.filter( - fasilitas=fasilitas).count() - return_json[fasilitas.id] = {} - fasilitas_details = return_json[fasilitas.id] - fasilitas_details["id"] = fasilitas.id - fasilitas_details["creator_email"] = user.email - fasilitas_details["place_id"] = fasilitas.lokasi.place_id - fasilitas_details["deskripsi"] = fasilitas.deskripsi - fasilitas_details["creator"] = fasilitas.user.last_name - fasilitas_details["date_time"] = fasilitas.date_time.strftime( - TIME_FORMAT) - fasilitas_details["like"] = fasilitas.like - fasilitas_details["dislike"] = fasilitas.dislike - fasilitas_details["rating"] = fasilitas.rating - fasilitas_details["tag"] = fasilitas.tag - fasilitas_details["disabilitas"] = fasilitas.disabilitas - fasilitas_details["jumlah"] = fasilitas.jumlah - fasilitas_details["image"] = '/media/'+str(fasilitas.image) - fasilitas_details["is_verified"] = fasilitas.is_verified - return JsonResponse(return_json, status=HTTPStatus.OK) + serializer = FasilitasSerializerRead(list_fasilitas, many=True) + data_response = serializer.data + new_dict = {item['id']: dict(item) for item in data_response} + return JsonResponse(new_dict, status=HTTPStatus.OK) except Exception as error: return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) @@ -128,28 +107,8 @@ def add_fasilitas(request, place_id): def detail_fasilitas(request, place_id, id): try: fasilitas = Fasilitas.objects.get(lokasi__place_id=place_id, id=id) - user = fasilitas.user - fasilitas.like = Likes.objects.filter(fasilitas=fasilitas).count() - fasilitas.dislike = Dislikes.objects.filter( - fasilitas=fasilitas).count() - return_json = {"id": fasilitas.id, - "place_id": fasilitas.lokasi.place_id, - "deskripsi": fasilitas.deskripsi, - "creator": user.last_name, - "creator_email": user.email, - "date_time": fasilitas.date_time.strftime(TIME_FORMAT), - "like": fasilitas.like, - "dislike": fasilitas.dislike, - "rating": fasilitas.rating, - "tag": fasilitas.tag, - "disabilitas": fasilitas.disabilitas, - "jumlah": fasilitas.jumlah, - "image": '/media/' + str(fasilitas.image), - "is_verified": fasilitas.is_verified} - return JsonResponse(return_json, status=HTTPStatus.OK) - except KeyError as missing_key: - return JsonResponse({'response': missing_key_message(str(missing_key))}, - status=HTTPStatus.BAD_REQUEST) + serializer = FasilitasSerializerRead(fasilitas, many=False) + return JsonResponse(serializer.data, status=HTTPStatus.OK) except Exception as error: return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) @@ -161,35 +120,15 @@ def update_fasilitas(request, place_id, id): try: fasilitas = Fasilitas.objects.get(lokasi__place_id=place_id, id=id) user_creator = fasilitas.user - desc = fasilitas.deskripsi - tag = fasilitas.tag - jumlah = fasilitas.jumlah - image = fasilitas.image - disabilitas = fasilitas.disabilitas if user_creator == request.user: - if 'deskripsi' in request.data.keys(): - desc = request.data['deskripsi'] - if 'tag' in request.data.keys(): - tag = request.data['tag'].split() - if 'jumlah' in request.data.keys(): - jumlah = request.data['jumlah'] - if 'image' in request.data.keys(): - image = request.data['image'] - if 'disabilitas' in request.data.keys(): - disabilitas = request.data['disabilitas'] - fasilitas.deskripsi = desc - fasilitas.tag = tag - fasilitas.jumlah = jumlah - fasilitas.image = image - fasilitas.disabilitas = disabilitas - fasilitas.save() + data = request.data.dict() + serializer = FasilitasSerializerWrite(fasilitas, data=data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() return JsonResponse({'response': '{} in fasilitas edited'.format(str(request.data.keys())), }, status=HTTPStatus.ACCEPTED) return JsonResponse({'response': 'Authentication failed'}, status=HTTPStatus.UNAUTHORIZED) - except KeyError as missing_key: - return JsonResponse({'response': missing_key_message(str(missing_key))}, - status=HTTPStatus.BAD_REQUEST) except Exception as error: return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) @@ -231,6 +170,7 @@ def update_like_fasilitas(request, place_id, id, operation): except KeyError as missing_key: return JsonResponse({'response': missing_key_message(str(missing_key))}, status=HTTPStatus.BAD_REQUEST) except Exception as error: + print(str(error)) return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) -- GitLab From 6eb4241a3546d961770662f3cb1e52a8b19acb5a Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 12:14:21 +0700 Subject: [PATCH 21/25] [REFACTOR] Fix duplicate (lokasi, tag) in fasilitas Object --- informasi_fasilitas/test_update_place_id.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/informasi_fasilitas/test_update_place_id.py b/informasi_fasilitas/test_update_place_id.py index b8330b0..609e34e 100644 --- a/informasi_fasilitas/test_update_place_id.py +++ b/informasi_fasilitas/test_update_place_id.py @@ -21,8 +21,11 @@ class UpdatePlaceIdTest(TestCase): "place_id": "IEUBJWLNDSOHIUJL", "timestamp": time - timedelta(days=120) }) + fasilitas_dict = factory.mock_fasilitas_test.copy() + fasilitas_dict.update({"tag": 'RT'}) fasilitas1 = factory.create_fasilitas_test(user=user, lokasi=lokasi2) - fasilitas2 = factory.create_fasilitas_test(user=user, lokasi=lokasi2) + fasilitas2 = factory.create_fasilitas_test(user=user, lokasi=lokasi2, + fasilitas_dict=fasilitas_dict) @patch('informasi_fasilitas.management.commands.update_place_id.requests.get') def test_lokasi_expired(self, mock_get): -- GitLab From 67f2e354efaec86a1857b7738345312d7e99e92c Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 13:13:46 +0700 Subject: [PATCH 22/25] [REFACTOR] Refactor Create Fasilitas API by using unique_toegther constraint and serializer --- .../migrations/0020_auto_20210601_0503.py | 22 +++++++++ informasi_fasilitas/models.py | 3 ++ informasi_fasilitas/views.py | 49 +++++++++---------- 3 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 informasi_fasilitas/migrations/0020_auto_20210601_0503.py diff --git a/informasi_fasilitas/migrations/0020_auto_20210601_0503.py b/informasi_fasilitas/migrations/0020_auto_20210601_0503.py new file mode 100644 index 0000000..d35236b --- /dev/null +++ b/informasi_fasilitas/migrations/0020_auto_20210601_0503.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.7 on 2021-06-01 05:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0019_auto_20210527_2251'), + ] + + operations = [ + migrations.AlterField( + model_name='kegiatan', + name='zona_waktu', + field=models.CharField(choices=[('WIB', 'WIB'), ('WITA', 'WITA'), ('WIT', 'WIT')], default='WIB', max_length=4), + ), + migrations.AlterUniqueTogether( + name='fasilitas', + unique_together={('lokasi', 'tag')}, + ), + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index ae59d6a..7d00bfb 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -79,6 +79,9 @@ class Fasilitas(models.Model): image = models.ImageField(upload_to="fasilitas/", null=True, default=None) is_verified = models.BooleanField(default=False) + class Meta: + unique_together = ('lokasi', 'tag') + class Kegiatan(models.Model): objects = models.Manager() diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index fe14ecd..f41065e 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -1,11 +1,13 @@ from http import HTTPStatus from django.http import JsonResponse +from django.db.utils import IntegrityError from rest_framework.decorators import api_view, permission_classes, authentication_classes from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import viewsets +from rest_framework.exceptions import ValidationError from notification.utils import send_komentar_notification from .serializers import LokasiSerializer, FasilitasSerializerRead, FasilitasSerializerWrite @@ -70,34 +72,28 @@ def list_fasilitas(request, place_id): def add_fasilitas(request, place_id): try: lokasi = Lokasi.objects.get(place_id=place_id) - deskripsi = request.POST['deskripsi'] - rating = request.POST['rating'] - tag = request.POST['tag'] - disabilitas = request.POST['disabilitas'] - jumlah = request.POST['jumlah'] - image = "" - if Fasilitas.objects.filter(lokasi=lokasi.pk, tag=tag).count() > 0: - return JsonResponse({'response': 'fasilitas already exist'}, - status=HTTPStatus.OK) - if tag == "": - return JsonResponse({'response': 'tag cannot be null'}, - status=HTTPStatus.BAD_REQUEST) - if 'image' in request.FILES.keys(): - image = request.FILES['image'] - fasilitas = Fasilitas.objects.create(lokasi=lokasi, - user=request.user, - deskripsi=deskripsi, - rating=rating, - tag=tag, - disabilitas=disabilitas, - jumlah=jumlah, - image=image) + user = request.user + data = request.data.dict() + data.update({"lokasi": lokasi.id, "user": user.id}) + serializer = FasilitasSerializerWrite(data=data) + serializer.is_valid(raise_exception=True) + fasilitas = serializer.save() return JsonResponse({'response': 'fasilitas added', 'id': fasilitas.id}, status=HTTPStatus.CREATED) - except KeyError as missing_key: - return JsonResponse({'response': missing_key_message(str(missing_key))}, - status=HTTPStatus.BAD_REQUEST) - except Exception as error: + except ValidationError as error: + error_detail = error.get_full_details() + if 'non_field_errors' in error_detail: + return JsonResponse({'response': 'fasilitas already exist'}, + status=HTTPStatus.OK) + if "tag" in error_detail: + if error_detail["tag"][0]["code"] == 'invalid_choice': + return JsonResponse({'response': 'tag cannot be null'}, + status=HTTPStatus.BAD_REQUEST) + else: + return JsonResponse({'response': "Bad Request. 'tag' key is needed"}, + status=HTTPStatus.BAD_REQUEST) + raise error + except Lokasi.DoesNotExist as error: return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) @@ -170,7 +166,6 @@ def update_like_fasilitas(request, place_id, id, operation): except KeyError as missing_key: return JsonResponse({'response': missing_key_message(str(missing_key))}, status=HTTPStatus.BAD_REQUEST) except Exception as error: - print(str(error)) return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) -- GitLab From a72ee74096ebf1d84d60812c35b00fe1c08e2218 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 16:46:28 +0700 Subject: [PATCH 23/25] [REFACTOR] Add cosntraint unique pair (user, fasilitas) in Likes and Dislikes --- .../migrations/0021_auto_20210601_0631.py | 23 +++++++++++++++++++ informasi_fasilitas/models.py | 6 +++++ 2 files changed, 29 insertions(+) create mode 100644 informasi_fasilitas/migrations/0021_auto_20210601_0631.py diff --git a/informasi_fasilitas/migrations/0021_auto_20210601_0631.py b/informasi_fasilitas/migrations/0021_auto_20210601_0631.py new file mode 100644 index 0000000..37927c1 --- /dev/null +++ b/informasi_fasilitas/migrations/0021_auto_20210601_0631.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.7 on 2021-06-01 06:31 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('informasi_fasilitas', '0020_auto_20210601_0503'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='dislikes', + unique_together={('user', 'fasilitas')}, + ), + migrations.AlterUniqueTogether( + name='likes', + unique_together={('user', 'fasilitas')}, + ), + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index 7d00bfb..6d5f10c 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -114,12 +114,18 @@ class Likes(models.Model): fasilitas = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) + class Meta: + unique_together = ('user', 'fasilitas') + class Dislikes(models.Model): objects = models.Manager() user = models.ForeignKey(User, on_delete=models.CASCADE) fasilitas = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) + class Meta: + unique_together = ('user', 'fasilitas') + class KomentarKegiatan(models.Model): objects = models.Manager() user = models.ForeignKey(User, on_delete=models.CASCADE) -- GitLab From 4b9ececa8e768e3ca4ddf584c1c4908ba87f5478 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Tue, 1 Jun 2021 16:47:11 +0700 Subject: [PATCH 24/25] [REFACTOR] Refactor Like & Dislike API using Serializer and constraint --- informasi_fasilitas/serializers.py | 14 ++++++++- informasi_fasilitas/views.py | 48 ++++++++++++++---------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 5fa260f..606a9fd 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers -from .models import Lokasi, Fasilitas, Kegiatan, KomentarKegiatan, FotoKegiatan +from .models import Lokasi, Fasilitas, Kegiatan, \ + KomentarKegiatan, FotoKegiatan, Likes, Dislikes import pytz TIMEZONE_INDONESIA = { @@ -86,6 +87,17 @@ class FasilitasSerializerRead(FasilitasSerializer): fields = FasilitasSerializer.Meta.fields + ('place_id', 'creator', "creator_email") +class LikesSerializer(serializers.ModelSerializer): + class Meta: + model = Likes + fields = '__all__' + + +class DislikesSerializer(serializers.ModelSerializer): + class Meta: + model = Dislikes + fields = '__all__' + class FotoKegiatanSerializer(serializers.ModelSerializer): place_id = serializers.CharField(source='kegiatan.lokasi.place_id', read_only=True) diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index f41065e..2420f03 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -10,7 +10,8 @@ from rest_framework import viewsets from rest_framework.exceptions import ValidationError from notification.utils import send_komentar_notification -from .serializers import LokasiSerializer, FasilitasSerializerRead, FasilitasSerializerWrite +from .serializers import LokasiSerializer, FasilitasSerializerRead,\ + FasilitasSerializerWrite, LikesSerializer, DislikesSerializer from .models import Lokasi, Fasilitas, Komentar, Likes, Dislikes from .permissions import UserPermission @@ -136,35 +137,32 @@ def update_like_fasilitas(request, place_id, id, operation): try: fasilitas = Fasilitas.objects.get(lokasi__place_id=place_id, id=id) user = request.user - - try: - like = Likes.objects.get(fasilitas=fasilitas, user=user) - except Likes.DoesNotExist: - like = None - try: - dislike = Dislikes.objects.get(fasilitas=fasilitas, user=user) - except Dislikes.DoesNotExist: - dislike = None + serializer_kwargs = {"user": user.id, "fasilitas": fasilitas.id} if operation == "like": - if like != None: - return JsonResponse({'response': "You have already liked this facility"}, status=HTTPStatus.ACCEPTED) - else: - Likes.objects.create(fasilitas=fasilitas, user=user) - if dislike != None: - dislike.delete() + try: + serializer = LikesSerializer(data=serializer_kwargs) + serializer.is_valid(raise_exception=True) + serializer.save() + Dislikes.objects.filter(**serializer_kwargs).delete() + except ValidationError: + return JsonResponse({'response': "You have already liked this facility"}, + status=HTTPStatus.ACCEPTED) elif operation == "dislike": - if dislike != None: - return JsonResponse({'response': "You have already disliked this facility"}, status=HTTPStatus.ACCEPTED) - else: - Dislikes.objects.create(fasilitas=fasilitas, user=user) - if like != None: - like.delete() + try: + serializer = DislikesSerializer(data=serializer_kwargs) + serializer.is_valid(raise_exception=True) + serializer.save() + Likes.objects.filter(**serializer_kwargs).delete() + except ValidationError: + return JsonResponse({'response': "You have already disliked this facility"}, + status=HTTPStatus.ACCEPTED) + fasilitas.like = Likes.objects.filter(fasilitas=fasilitas).count() fasilitas.dislike = Dislikes.objects.filter( fasilitas=fasilitas).count() - return JsonResponse({'response': "You have successfuly {}d this facility".format(operation)}, status=HTTPStatus.CREATED) - except KeyError as missing_key: - return JsonResponse({'response': missing_key_message(str(missing_key))}, status=HTTPStatus.BAD_REQUEST) + fasilitas.save() + return JsonResponse({'response': "You have successfuly {}d this facility".format(operation)}, + status=HTTPStatus.CREATED) except Exception as error: return JsonResponse({'response': str(error)}, status=HTTPStatus.NOT_FOUND) -- GitLab From 74cd5f543c4f869ad5fd7598524462f931a77a3d Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Thu, 3 Jun 2021 00:07:51 +0700 Subject: [PATCH 25/25] [CHORES] Change request body add/update images key to images[] --- informasi_fasilitas/test_views_kegiatan.py | 14 +++++++------- informasi_fasilitas/views_kegiatan.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index c9135b7..a5e71ae 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -51,9 +51,9 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): image2 = SimpleUploadedFile("test2.jpg", content=open(image_path2, 'rb').read(), content_type='image/jpeg') - self.kegiatan_images = {'images': [image1, image2]} + self.kegiatan_images = {'images[]': [image1, image2]} self.foto = None - for image in self.kegiatan_images["images"]: + for image in self.kegiatan_images["images[]"]: foto = FotoKegiatan.objects.create(kegiatan=self.kegiatan, foto=image) self.foto = foto image.seek(0) @@ -286,7 +286,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'time_start': self.kegiatan_time_start, 'time_end': self.kegiatan_time_end}) - send_data.pop("images") + send_data.pop("images[]") expected_json.update(send_data) self.assertEqual(response.status_code, HTTPStatus.ACCEPTED) self.assertDictEqual(data, expected_json) @@ -321,14 +321,14 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): counter += 1 def test_can_add_foto_kegiatan(self): - data = {"foto": self.kegiatan_images["images"][0]} + 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]} + data = {"foto": self.kegiatan_images["images[]"][0]} url = reverse('add-foto-kegiatan', kwargs={ 'place_id': "IDSNKCM", 'kegiatan_id': 222, @@ -339,7 +339,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(len(kegiatan_fotos), 2) def test_can_put_update_foto_kegiatan(self): - data = {"foto": self.kegiatan_images["images"][0]} + 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() @@ -347,7 +347,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertNotEqual(self.foto.foto, output.get("foto")) def test_failed_put_update_foto_kegiatan(self): - data = {"foto": self.kegiatan_images["images"][0]} + data = {"foto": self.kegiatan_images["images[]"][0]} url = reverse('update-foto-kegiatan', kwargs={ 'place_id': "IDSNKCM", 'kegiatan_id': 222, diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index fb938e0..4b33dab 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -55,12 +55,12 @@ def add_kegiatan(request, place_id): user = request.user data = request.data.dict() data.update({"user": user.id, "lokasi": lokasi.id}) - data.pop("images", []) + data.pop("images[]", []) serializer = KegiatanSerializer(data=data) serializer.is_valid(raise_exception=True) kegiatan = serializer.save() - serializer = _add_foto_kegiatan(kegiatan, request.data.getlist("images")) + serializer = _add_foto_kegiatan(kegiatan, request.data.getlist("images[]")) return JsonResponse(_clean_json_kegiatan(serializer.data), status=HTTPStatus.CREATED) except Lokasi.DoesNotExist: @@ -74,11 +74,11 @@ def update_kegiatan(request, place_id, id): try: kegiatan = Kegiatan.objects.get(lokasi__place_id=place_id, id=id) data = request.data.dict() - data.pop("images", []) + data.pop("images[]", []) serializer = KegiatanSerializer(kegiatan, data=data, partial=True) serializer.is_valid(raise_exception=True) kegiatan = serializer.save() - serializer = _add_foto_kegiatan(kegiatan, request.data.getlist("images")) + serializer = _add_foto_kegiatan(kegiatan, request.data.getlist("images[]")) return JsonResponse(_clean_json_kegiatan(serializer.data), status=HTTPStatus.ACCEPTED) except Kegiatan.DoesNotExist: -- GitLab