From 76c11fb10913ba663d4059fb2f5af0bbe582ad53 Mon Sep 17 00:00:00 2001 From: "alvin.hariman" Date: Tue, 18 May 2021 10:39:28 +0700 Subject: [PATCH 01/48] [REFACTOR] Remove unused code --- new_rest_api/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/new_rest_api/views.py b/new_rest_api/views.py index 4e6ec62..d0b2cf4 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -61,6 +61,10 @@ def user_details(request, email): "email": user.email, "phone_number": bisa_go_user.phone_number} json_return.update(serializer.data) + if (json_return["seen"] == False) : + json_return.pop("phone_number") + json_return.pop("email") + json_return.pop("alamat") return JsonResponse(json_return, safe=False, status=status.OK) else: return JsonResponse({'response' : request_error_message("get")}, @@ -78,13 +82,12 @@ def register_user(request): email = request.POST['email'] password = request.POST['password'] data = dict(list(request.POST.dict().items())[3:]) - + data['user'] = user.pk + data['seen'] = True user = User.objects.create_user(username=email, email=email, password=password, last_name=name) user.is_active = False user.save() - data['user'] = user.pk - data['seen'] = True mail_subject = "Activate your account" message = render_to_string('acc_active_email.html', { 'user' : user, @@ -123,8 +126,6 @@ def activate(request, uidb64, token): if user is not None and account_activation_token.check_token(user, token): user.is_active = True user.save() - # login(request, user) - # return redirect('home') return JsonResponse({'response' : 'User activated'}, status=status.CREATED) else: return JsonResponse({'response' : request_error_message('get')}, @@ -138,8 +139,6 @@ def activate(request, uidb64, token): def update_user(request): try: if request.method == 'POST': - name = request.POST['name'] - email = request.POST['email'] data = dict(list(request.POST.dict().items())) if request.FILES : type = request.FILES['foto'].content_type.split("/")[0] @@ -148,6 +147,7 @@ def update_user(request): else : return JsonResponse({'response': 'File is not image type'}, safe=False, status=status.INTERNAL_SERVER_ERROR) + email = request.POST['email'] user = User.objects.get(username=email) data["user"] = user.pk bisa_go_user = BisaGoUser.objects.get(user=user) -- GitLab From d99a7af379d99468b3a1da8187773ee375bce542 Mon Sep 17 00:00:00 2001 From: "alvin.hariman" Date: Tue, 18 May 2021 13:53:54 +0700 Subject: [PATCH 02/48] [REFACTOR] Remove unused code and simplify test code --- informasi_fasilitas/test_views_lokasi.py | 1 - layanan_khusus/tests.py | 4 -- new_rest_api/tests.py | 77 +++++++----------------- registrasi/tests.py | 5 +- 4 files changed, 22 insertions(+), 65 deletions(-) diff --git a/informasi_fasilitas/test_views_lokasi.py b/informasi_fasilitas/test_views_lokasi.py index ab304c6..5ce187c 100644 --- a/informasi_fasilitas/test_views_lokasi.py +++ b/informasi_fasilitas/test_views_lokasi.py @@ -13,7 +13,6 @@ from pplbackend.utils import response_decode class LokasiRelatedViewTest(InformasiFasilitasViewTest): - def setUp(self): super().setUp() self.list_lokasi_url = reverse('list-lokasi') diff --git a/layanan_khusus/tests.py b/layanan_khusus/tests.py index 19c698d..1ec1661 100644 --- a/layanan_khusus/tests.py +++ b/layanan_khusus/tests.py @@ -136,7 +136,6 @@ def auth_setup(): class LayananKhususModelTest(TestCase): - def test_models_sekolah_not_created(self): with self.assertRaises(IntegrityError) as ex: obj = Sekolah(name=None) @@ -170,9 +169,7 @@ class LayananKhususModelTest(TestCase): count = Komunitas.objects.all().count() self.assertNotEqual(count, 0) - class LayananKhususViewsTest(TestCase): - urlpatterns = [ path('layanan-khusus/', include('layanan_khusus.urls')), ] @@ -297,7 +294,6 @@ class LayananKhususViewsTest(TestCase): class LayananKhususSearchTest(TestCase): - urlpatterns = [ path('layanan-khusus/', include('layanan_khusus.urls')), ] diff --git a/new_rest_api/tests.py b/new_rest_api/tests.py index 81b9256..e37a1ab 100644 --- a/new_rest_api/tests.py +++ b/new_rest_api/tests.py @@ -10,6 +10,16 @@ from .tokens import account_activation_token import io import requests +MOCK_USER = {'name': 'Astraykai', + 'email':'astraykai@gmail.com', + 'password':'chingchenghanji', + 'phone_number':'089892218567', + 'tanggal_lahir':'1990-05-05', + 'jenis_kelamin':'Laki-laki', + 'disabilitas':'', + 'pekerjaan':'Mahasiswa', + 'alamat':'Alamat Palsu'} + class UserTests(APITestCase): urlpatterns = [ path('api/', include('new_rest_api.urls')), @@ -17,15 +27,7 @@ class UserTests(APITestCase): def setUp(self): url = reverse('create-user') - data = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'password':'chingchenghanji', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu'} + data = MOCK_USER self.client.post(url, data) def test_create_user(self): @@ -81,16 +83,8 @@ class UserTests(APITestCase): def test_user_already_exist(self): url = reverse('create-user') - data = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'password':'chingchenghanji', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu', - 'is_active': True} + data = MOCK_USER + data["is_active"] = True response = self.client.post(url, data) self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) json_response = json.loads(response.content) @@ -98,16 +92,8 @@ class UserTests(APITestCase): def test_invalid_request(self): url = reverse('user-list') - data = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'password':'chingchenghanji', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu', - 'is_active': True} + data = MOCK_USER + data["is_active"] = True response = self.client.post(url, data) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) json.loads(response.content) @@ -137,14 +123,7 @@ class UserTests(APITestCase): It will successfully to update the user ''' url = reverse('update-user') - data = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu'} + data = MOCK_USER response = self.client.post(url, data) json_response = json.loads(response.content) self.assertEqual(json_response['response'], 'User updated') @@ -175,14 +154,8 @@ class UserTests(APITestCase): It will failed to update the user ''' url = reverse('update-user') - data = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'phone_number':'', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu'} + data = MOCK_USER + data["phone_number"] = "" response = self.client.post(url, data) json_response = json.loads(response.content) self.assertEqual(json_response['response'], 'Internal server error') @@ -193,15 +166,8 @@ class UserTests(APITestCase): ''' url = reverse('update-user') - data = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu', - 'foto':(io.BytesIO(b"this is a test"), 'test.pdf')} + data = MOCK_USER + data["foto"] = (io.BytesIO(b"this is a test"), 'test.pdf') response = self.client.post(url, data, headers={'Content-Type': "multipart/form-data"}) json_response = json.loads(response.content) self.assertEqual(json_response['response'], 'File is not image type') @@ -227,8 +193,7 @@ class UserTests(APITestCase): url = reverse('user-details', kwargs={'email':'astrayyahoo@gmail.com'}) response = self.client.get(url, format='json') json_test = json.loads(response.content) - print(json_test) self.assertEqual(json_test['seen'], True) class InfoTests(APITestCase, URLPatternsTestCase): - pass + pass \ No newline at end of file diff --git a/registrasi/tests.py b/registrasi/tests.py index 32f7ca8..62e1fb5 100644 --- a/registrasi/tests.py +++ b/registrasi/tests.py @@ -3,9 +3,6 @@ from django.contrib.auth.models import User from django.db.utils import IntegrityError from .models import BisaGoUser - - - class RegistrationTest(TestCase): def test_user_model_created(self): user = User.objects.create(username="hoho", email="user@gmail.com", password="hohoho") @@ -17,4 +14,4 @@ class RegistrationTest(TestCase): with self.assertRaises(IntegrityError) as ex: obj = BisaGoUser(user=None) obj.save() - self.assertEqual(ex.expected, IntegrityError) + self.assertEqual(ex.expected, IntegrityError) \ No newline at end of file -- GitLab From 6844659b302e01f24c81abb4194b2300ae277314 Mon Sep 17 00:00:00 2001 From: "alvin.hariman" Date: Tue, 18 May 2021 14:18:23 +0700 Subject: [PATCH 03/48] [REFACTOR] Simplify code in views.py --- layanan_khusus/views.py | 24 +++++------------------- new_rest_api/views.py | 4 ++-- oauth/views.py | 18 +----------------- 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/layanan_khusus/views.py b/layanan_khusus/views.py index 251c0c2..ad646bf 100644 --- a/layanan_khusus/views.py +++ b/layanan_khusus/views.py @@ -8,6 +8,7 @@ from rest_framework.permissions import IsAuthenticated from .models import Sekolah, Komunitas from .serializers import SekolahSerializer, PenyandangSerializer, KomunitasSerializer from django.contrib.postgres.search import SearchVector, SearchQuery +from django.forms.models import model_to_dict @api_view(['GET']) @authentication_classes([]) @@ -86,28 +87,13 @@ def pencarian(request): return_json = {} for sekolah in list_sekolah: - return_json[indeks] = {} - sekolah_details = return_json[indeks] - sekolah_details["name"] = sekolah.name - sekolah_details["alamat"] = sekolah.alamat - sekolah_details["no_telp"] = sekolah.no_telp - sekolah_details["website"] = sekolah.website - sekolah_details["jumlah_siswa"] = sekolah.jumlah_siswa - sekolah_details["status"] = sekolah.status - sekolah_details["jenis_sekolah"] = sekolah.jenis_sekolah - sekolah_details["akreditasi"] = sekolah.akreditasi - + return_json[indeks] = model_to_dict(sekolah) + return_json[indeks].pop("id") indeks += 1 for komunitas in list_komunitas: - return_json[indeks] = {} - komunitas_details = return_json[indeks] - komunitas_details["name"] = komunitas.name - komunitas_details["alamat"] = komunitas.alamat - komunitas_details["no_telp"] = komunitas.no_telp - komunitas_details["website"] = komunitas.website - komunitas_details["jenis_komunitas"] = komunitas.jenis_komunitas - + return_json[indeks] = model_to_dict(komunitas) + return_json[indeks].pop("id") indeks += 1 return JsonResponse(return_json, status=HTTPStatus.OK) diff --git a/new_rest_api/views.py b/new_rest_api/views.py index d0b2cf4..628bb68 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -81,11 +81,11 @@ def register_user(request): name = request.POST['name'] email = request.POST['email'] password = request.POST['password'] + user = User.objects.create_user(username=email, email=email, + password=password, last_name=name) data = dict(list(request.POST.dict().items())[3:]) data['user'] = user.pk data['seen'] = True - user = User.objects.create_user(username=email, email=email, - password=password, last_name=name) user.is_active = False user.save() mail_subject = "Activate your account" diff --git a/oauth/views.py b/oauth/views.py index d516f86..43e1267 100644 --- a/oauth/views.py +++ b/oauth/views.py @@ -13,8 +13,6 @@ from django.contrib.auth.models import User from django.conf import settings from registrasi.models import BisaGoUser - - @csrf_exempt @api_view(http_method_names=['POST']) @permission_classes([]) @@ -32,7 +30,6 @@ def request_token(request): user = result return _check_user(user) - def _google_check(access_token, name): try: result = validate_google_token(access_token) @@ -44,7 +41,6 @@ def _google_check(access_token, name): user = _create_google_user(email=result, name=name) return user - def _check_normal_auth(request, email, password): try: user = authenticate(request, username=email, password=password) @@ -55,7 +51,6 @@ def _check_normal_auth(request, email, password): raise NotFound(detail="User doesn't exist") return user - def _check_user(user): if user.is_active: token, create = Token.objects.get_or_create(user=user) @@ -64,7 +59,6 @@ def _check_user(user): else: raise AuthenticationFailed(detail="Please activate your account") - def _create_google_user(email, name): try: return User.objects.get(username=email) @@ -82,7 +76,6 @@ def _create_google_user(email, name): BisaGoUser.objects.create(user=user, phone_number=random_generated_phone_number) return user - def _create_random_phone_number(): phone_number = 'x'.join([str(random.randint(0, 9)) for _ in range(8)]) try: @@ -91,7 +84,6 @@ def _create_random_phone_number(): except BisaGoUser.DoesNotExist: return phone_number - def validate_google_token(access_token): payload = {'access_token': access_token} # validate the token req = requests.get('https://www.googleapis.com/oauth2/v2/userinfo', @@ -99,12 +91,4 @@ def validate_google_token(access_token): data = json.loads(req.text) if 'error' in data or 'email' not in data: raise AuthenticationFailed(detail='Wrong google token / this google token is already expired.') - return data.get("email") - - - - - - - - + return data.get("email") \ No newline at end of file -- GitLab From c2a8000e1cdb1bf0e48b5eb411c4d113f8193192 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Wed, 19 May 2021 18:49:59 +0700 Subject: [PATCH 04/48] [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 05/48] [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 06/48] [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 07/48] [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 08/48] [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 09/48] [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 4870f50d38edd05e8b6199d09f844eea7e4a18c4 Mon Sep 17 00:00:00 2001 From: Christopher Samuel Date: Sat, 22 May 2021 17:10:17 +0700 Subject: [PATCH 10/48] Revert "[GREEN] Implemented search Kegiatan to dev API + fixed minor logic issue in nearest Kegiatan" This reverts commit 89b9dca0dfde47d4b073bac23cf8c36932571bce. --- informasi_fasilitas/test_views_kegiatan.py | 32 ++++++++++++++++++++-- informasi_fasilitas/urls.py | 3 ++ informasi_fasilitas/views_kegiatan.py | 25 +++++++++++------ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index cd84115..76dcf03 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -29,6 +29,8 @@ 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_kegiatan_id = {'kegiatan_id' : self.kegiatan.lokasi.place_id} @@ -52,8 +54,10 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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) @@ -212,3 +216,27 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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) + content = json.loads(response.content.decode('utf-8')) + expected_json = { + str(self.kegiatan.id): { + '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, + }, + } + 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) diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 9529f3e..6442517 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -38,6 +38,9 @@ urlpatterns = [ path('lokasi/kegiatan-terdekat', views_kegiatan.nearest_kegiatan, name='nearest-kegiatan'), + + path('lokasi/search-kegiatan/', + views_kegiatan.search_kegiatan, name='search-kegiatan'), path('lokasi/add-kegiatan//', views_kegiatan.add_kegiatan, name='add-kegiatan'), diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 7bf9c2c..3228f1a 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -95,11 +95,24 @@ 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(['GET']) +@authentication_classes([]) +@permission_classes([]) +def search_kegiatan(request, query): + query_by_nama = Kegiatan.objects.filter(nama_kegiatan__icontains=query) + query_by_deskripsi = Kegiatan.objects.filter(deskripsi__icontains=query) + query_by_penyelenggara = Kegiatan.objects.filter(penyelenggara__icontains=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) + new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} + return JsonResponse(new_dict, status=HTTPStatus.OK) def _clean_json_kegiatan(kegiatan): - kegiatan.pop("user") - kegiatan.pop("lokasi") - return kegiatan + kegiatan.pop("user") + kegiatan.pop("lokasi") + return kegiatan def _add_foto_kegiatan(kegiatan, list_image): @@ -114,9 +127,3 @@ def _create_list_kegiatan_foto(kegiatan, list_image): foto = FotoKegiatan.objects.create(kegiatan=kegiatan, foto=image) list_kegiatan_foto.append(foto) return list_kegiatan_foto - - - - - - -- GitLab From 14e814df2b7323f5194ed61254fad566ecdaac23 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Mon, 24 May 2021 12:40:38 +0700 Subject: [PATCH 11/48] [RED] Tests for notification --- informasi_fasilitas/test_admin.py | 86 ++++++++++++ informasi_fasilitas/test_models.py | 17 ++- notification/test_base.py | 38 ++++++ notification/test_permissions.py | 67 ++++++++++ notification/test_utils.py | 204 +++++++++++++++++++++++++++++ notification/test_views.py | 203 ++++++++++++++++++++++++++++ requirements.txt | 5 + 7 files changed, 616 insertions(+), 4 deletions(-) create mode 100644 informasi_fasilitas/test_admin.py create mode 100644 notification/test_base.py create mode 100644 notification/test_permissions.py create mode 100644 notification/test_utils.py create mode 100644 notification/test_views.py diff --git a/informasi_fasilitas/test_admin.py b/informasi_fasilitas/test_admin.py new file mode 100644 index 0000000..335a601 --- /dev/null +++ b/informasi_fasilitas/test_admin.py @@ -0,0 +1,86 @@ +from unittest.mock import MagicMock, patch +from django_mock_queries.query import MockSet + +from django.contrib import messages +from django.contrib.admin.sites import AdminSite +from django.test import TestCase +from django.utils.translation import gettext_lazy as _ + +from .models import Komentar +from .admin import KomentarAdmin + +class MockSuperUser: + def has_perm(self, perm, obj=None): + return True + +class MockRequest: + def __init__(self): + self.GET = [] + self.user = MockSuperUser() + +request = MockRequest() + +class TestAdmin(TestCase): + result_success = {'success':1} + result_failed = {'success':0} + + def setUp(self): + self.site = AdminSite() + self.komentar_admin = KomentarAdmin(Komentar, self.site) + + self.mock_fcm_device = MagicMock() + self.mock_fcm_device.__str__.return_value = 'ariq' + self.mock_fcm_device_query = MockSet(self.mock_fcm_device) + + self.mock_komentar = MagicMock() + self.mock_komentar_query = MockSet(self.mock_komentar) + + def test_list_display(self): + list_display = self.komentar_admin.get_list_display(request) + self.assertEqual(list_display, ('__str__', 'deskripsi', 'user', + 'notify_to')) + + def test_list_actions(self): + list_action = self.komentar_admin.get_action_choices(request) + self.assertIn(('send_notification', 'Send notification'), list_action) + + @patch('informasi_fasilitas.admin.KomentarAdmin.message_user') + @patch('informasi_fasilitas.admin.get_target_fcm_device') + @patch('informasi_fasilitas.admin.send_komentar_notification') + def test_action_send_notification_success(self, mock_send_komentar_notif, + mock_target_fcm, mock_message_user): + mock_send_komentar_notif.return_value = self.result_success + mock_target_fcm.return_value = self.mock_fcm_device_query + + self.komentar_admin.send_notification(request, self.mock_komentar_query) + + msg = _('sent to ariq, detail: %r' % self.result_success) + + mock_target_fcm.assert_called_once_with(self.mock_komentar) + mock_message_user.assert_called_once_with(request, msg) + + @patch('informasi_fasilitas.admin.KomentarAdmin.message_user') + @patch('informasi_fasilitas.admin.get_target_fcm_device') + @patch('informasi_fasilitas.admin.send_komentar_notification') + def test_action_send_notification_failed(self, mock_send_komentar_notif, + mock_target_fcm, mock_message_user): + mock_send_komentar_notif.return_value = self.result_failed + mock_target_fcm.return_value = self.mock_fcm_device_query + + self.komentar_admin.send_notification(request, self.mock_komentar_query) + + msg = _('failed to send, detail: %r' % self.result_failed) + + mock_target_fcm.assert_not_called() + mock_message_user.assert_called_once_with(request, msg, + level=messages.WARNING) + + + @patch('informasi_fasilitas.admin.get_target_fcm_device') + def test_list_display_notify_to(self, mock_target_fcm): + mock_target_fcm.return_value = self.mock_fcm_device_query + + ret = self.komentar_admin.notify_to(self.mock_komentar) + + self.assertEqual(ret, self.mock_fcm_device) + mock_target_fcm.assert_called_once_with(self.mock_komentar) diff --git a/informasi_fasilitas/test_models.py b/informasi_fasilitas/test_models.py index fae0d4c..214c04e 100644 --- a/informasi_fasilitas/test_models.py +++ b/informasi_fasilitas/test_models.py @@ -1,7 +1,8 @@ from django.db.utils import IntegrityError from .test_base import InformasiFasilitasTest -from .models import Lokasi, Fasilitas, Komentar, Likes, Dislikes, Kegiatan, FotoKegiatan, KomentarKegiatan +from .models import (Lokasi, Fasilitas, Komentar, Likes, Dislikes, + Kegiatan, FotoKegiatan, KomentarKegiatan) class InformasiFasilitasModelTest(InformasiFasilitasTest): @@ -57,6 +58,10 @@ class InformasiFasilitasModelTest(InformasiFasilitasTest): count = Komentar.objects.all().count() self.assertNotEqual(count, 0) + def test_models_komentar_str(self): + komentar = self.create_komentar_test() + self.assertEqual(str(komentar), komentar.deskripsi) + def test_models_dislikes_not_created(self): with self.assertRaises(IntegrityError) as ex: obj = Dislikes(fasilitas=None) @@ -78,18 +83,22 @@ class InformasiFasilitasModelTest(InformasiFasilitasTest): self.create_likes_test() count = Likes.objects.all().count() self.assertNotEqual(count, 0) - + def test_models_create_new_kegiatan(self): self.create_kegiatan_test() count = Kegiatan.objects.all().count() self.assertNotEqual(count, 0) - + def test_models_create_new_foto_kegiatan(self): self.create_foto_kegiatan_test() count = FotoKegiatan.objects.all().count() self.assertNotEqual(count, 0) - + def test_models_create_new_komentar_kegiatan(self): self.create_komentar_kegiatan_test() count = KomentarKegiatan.objects.all().count() self.assertNotEqual(count, 0) + + def test_models_komentar_kegiatan_str(self): + komentar = self.create_komentar_kegiatan_test() + self.assertEqual(str(komentar), komentar.deskripsi) diff --git a/notification/test_base.py b/notification/test_base.py new file mode 100644 index 0000000..dac147b --- /dev/null +++ b/notification/test_base.py @@ -0,0 +1,38 @@ +from django.contrib.auth.models import User +from django.test import TestCase + +from fcm_django.models import FCMDevice + +from pplbackend.utils import get_client_login_with_user + +class BaseTestNotification(TestCase): + simple_token = 'simple token' + mock_user_test = { + 'username': 'mock username', + 'email': 'self.mock_user@test.com', + 'last_name': 'mock last_name', + } + + mock_notification_device_test = { + 'registration_id': simple_token, + 'type': 'android', + } + + def create_user_test(self, user_dict=mock_user_test): + return User.objects.create_user(**user_dict) + + def create_notification_device_test( + self, + user_dict=mock_user_test, + user=None, + notification_device_dict=mock_notification_device_test + ): + return FCMDevice.objects.create( + **notification_device_dict, + user=self.create_user_test(user_dict=user_dict) if user is None else user + ) + + def setUp(self): + self.user = self.create_user_test() + self.device = self.create_notification_device_test(user=self.user) + self.client = get_client_login_with_user(self.user) diff --git a/notification/test_permissions.py b/notification/test_permissions.py new file mode 100644 index 0000000..29ff48d --- /dev/null +++ b/notification/test_permissions.py @@ -0,0 +1,67 @@ +from django.test import TestCase +from .permissions import NotificationPermission + +class MockUserAuthenticated: + is_authenticated = True + +class MockUserNotAuthenticated: + is_authenticated = False + +class MockRequest: + def __init__(self, user): + self.user = user + +class MockView: + def __init__(self, action): + self.action = action + +class TestPermission(TestCase): + def setUp(self): + self.notification_permission = NotificationPermission() + self.authenticated_request = MockRequest(MockUserAuthenticated()) + self.not_authenticated_request = MockRequest(MockUserNotAuthenticated()) + + self.view_list = MockView('list') + self.view_create = MockView('create') + self.view_update = MockView('update') + self.view_delete = MockView('delete') + + def test_authenticated_can_list(self): + ret = self.notification_permission\ + .has_permission(self.authenticated_request, self.view_list) + self.assertTrue(ret) + + def test_not_authenticated_cant_list(self): + ret = self.notification_permission\ + .has_permission(self.not_authenticated_request, self.view_list) + self.assertFalse(ret) + + def test_authenticated_can_create(self): + ret = self.notification_permission\ + .has_permission(self.authenticated_request, self.view_create) + self.assertTrue(ret) + + def test_not_authenticated_cant_create(self): + ret = self.notification_permission\ + .has_permission(self.not_authenticated_request, self.view_create) + self.assertFalse(ret) + + def test_authenticated_cant_update(self): + ret = self.notification_permission\ + .has_permission(self.authenticated_request, self.view_update) + self.assertFalse(ret) + + def test_not_authenticated_cant_update(self): + ret = self.notification_permission\ + .has_permission(self.not_authenticated_request, self.view_update) + self.assertFalse(ret) + + def test_authenticated_cant_delete(self): + ret = self.notification_permission\ + .has_permission(self.authenticated_request, self.view_delete) + self.assertFalse(ret) + + def test_not_authenticated_cant_delete(self): + ret = self.notification_permission\ + .has_permission(self.not_authenticated_request, self.view_delete) + self.assertFalse(ret) diff --git a/notification/test_utils.py b/notification/test_utils.py new file mode 100644 index 0000000..151857c --- /dev/null +++ b/notification/test_utils.py @@ -0,0 +1,204 @@ +from unittest.mock import patch, MagicMock + +from django.db.models import QuerySet +from django.contrib.auth.models import User +from fcm_django.models import FCMDevice + +from informasi_fasilitas.models import (Komentar, KomentarKegiatan, + Fasilitas, Kegiatan, Lokasi) +from . import utils +from .test_base import BaseTestNotification + +class TestUtilsNotification(BaseTestNotification): + + def test_get_type_informasi_from_komentar_should_return_fasilitas(self): + komentar = Komentar() + type_fasilitas = utils.get_type_informasi_from_komentar(komentar) + self.assertEqual(type_fasilitas, 'fasilitas') + + def test_get_type_informasi_from_komentarkegiatan_should_return_kegiatan(self): + komentar_kegiatan = KomentarKegiatan() + type_kegiatan = utils.get_type_informasi_from_komentar(komentar_kegiatan) + self.assertEqual(type_kegiatan, 'kegiatan') + + def test_get_informasi_from_komentar_should_return_fasilitas(self): + fasilitas = Fasilitas() + komentar = Komentar(fasilitas=fasilitas) + ret_fasilitas = utils.get_informasi(komentar) + self.assertTrue(isinstance(ret_fasilitas, Fasilitas)) + + def test_get_informasi_from_komentarkegiatan_should_return_kegiatan(self): + kegiatan = Kegiatan() + komentar_kegiatan = KomentarKegiatan(kegiatan=kegiatan) + ret_kegiatan = utils.get_informasi(komentar_kegiatan) + self.assertTrue(isinstance(ret_kegiatan, Kegiatan)) + + def test_get_place_id_from_komentar_should_return_its_place_id(self): + place_id = 'dummy_place_id' + lokasi = Lokasi(place_id=place_id) + fasilitas = Fasilitas(lokasi=lokasi) + komentar = Komentar(fasilitas=fasilitas) + + ret_place_id = utils.get_place_id_from_komentar(komentar) + + self.assertEqual(ret_place_id, place_id) + + def test_get_place_id_from_komentar_kegiatan_should_return_its_place_id(self): + place_id = 'dummy_place_id' + lokasi = Lokasi(place_id=place_id) + kegiatan = Kegiatan(lokasi=lokasi) + komentar = KomentarKegiatan(kegiatan=kegiatan) + + ret_place_id = utils.get_place_id_from_komentar(komentar) + + self.assertEqual(ret_place_id, place_id) + + def test_get_informasi_id_from_komentar_should_return_its_informasi_id(self): + _id = 8 + fasilitas = Fasilitas(id=_id) + komentar = Komentar(fasilitas=fasilitas) + + ret_place_id = utils.get_informasi_id_from_komentar(komentar) + + self.assertEqual(ret_place_id, _id) + + def test_get_informasi_id_from_komentar_kegiatan_should_return_its_informasi_id(self): + _id = 9 + kegiatan = Kegiatan(id=_id) + komentar = KomentarKegiatan(kegiatan=kegiatan) + + ret_place_id = utils.get_informasi_id_from_komentar(komentar) + + self.assertEqual(ret_place_id, _id) + + def test_get_sender_last_name_from_komentar_not_null(self): + user = User(last_name='ariqbasyar') + komentar = Komentar(user=user) + + ret_last_name = utils.get_sender_last_name_from_komentar(komentar) + + self.assertEqual(ret_last_name, user.last_name) + + def test_get_target_fcm_device_should_return_its_fasilitas_creator_active_fcmdevice(self): + user = User(username='ariqbasyar') + user.save() + + user_sender = User() + + fcmdevice = FCMDevice(user=user, registration_id='dummy token', type='android') + fcmdevice.save() + + fasilitas = Fasilitas(user=user) + komentar = Komentar(fasilitas=fasilitas, user=user_sender) + + ret_fcm_device = utils.get_target_fcm_device(komentar) + + self.assertEqual(len(ret_fcm_device), 1) + self.assertEqual(ret_fcm_device.first().id, fcmdevice.id) + self.assertEqual(ret_fcm_device.first().registration_id, fcmdevice.registration_id) + self.assertEqual(ret_fcm_device.first().user.username, user.username) + + def test_get_target_fcm_device_should_not_return_its_fasilitas_creator_inactive_fcmdevice(self): + user = User() + user.save() + + username_sender = 'ariqbasyar2' + user_sender = User(username=username_sender) + + fcmdevice = FCMDevice(user=user, registration_id='dummy token', + type='android', active=False) + fcmdevice.save() + + fasilitas = Fasilitas(user=user) + komentar = Komentar(fasilitas=fasilitas, user=user_sender) + + ret_fcm_device = utils.get_target_fcm_device(komentar) + + self.assertEqual(len(ret_fcm_device), 0) + + def test_get_target_fcm_device_should_not_return_its_fasilitas_creator_fcmdevice_if_same_as_komentar_sender(self): + user = User() + user.save() + + fcmdevice = FCMDevice(user=user, registration_id='dummy token', type='android') + fcmdevice.save() + + fasilitas = Fasilitas(user=user) + komentar = Komentar(fasilitas=fasilitas, user=user) + + ret_fcm_device = utils.get_target_fcm_device(komentar) + + self.assertEqual(len(ret_fcm_device), 0) + + def test_get_formatted_username_length_less_than_max_length_name_should_not_get_trimmed(self): + username = 'ariq' + ret_username = utils.get_formatted_user_name(username, max_length_name=10) + self.assertEqual(username, ret_username) + + def test_get_formatted_username_length_equal_max_length_name_should_not_get_trimmed(self): + username = 'ariqbasyar' + ret_username = utils.get_formatted_user_name(username, max_length_name=10) + self.assertEqual(username, ret_username) + + def test_get_formatted_username_length_more_than_max_length_name_should_get_trimmed(self): + username = 'ariqbasyarr' + + max_length_name = 10 + replacement_str = '...' + length_replacement = len(replacement_str) + ret_username = utils.get_formatted_user_name(username, max_length_name=max_length_name, + replacement_str=replacement_str) + + self.assertEqual(ret_username, '%s%s' % (username[:max_length_name - length_replacement], + replacement_str)) + self.assertEqual(ret_username, 'ariqbas...') + + def test_get_formatted_message_username_should_not_get_trimmed(self): + username = 'ariqbasyar' + ret_formatted_message = utils.get_formatted_message(username) + self.assertEqual(ret_formatted_message, 'ariqbasyar menambahkan komentar baru') + + def test_get_formatted_message_username_should_get_trimmed(self): + username = 'ariqbasyarr' + ret_formatted_message = utils.get_formatted_message(username) + self.assertEqual(ret_formatted_message, 'ariqbas... menambahkan komentar baru') + + @patch('notification.utils.get_target_fcm_device') + @patch('notification.utils.get_formatted_message') + @patch('notification.utils.get_sender_last_name_from_komentar') + @patch('notification.utils.get_type_informasi_from_komentar') + @patch('notification.utils.get_informasi_id_from_komentar') + @patch('notification.utils.get_place_id_from_komentar') + def test_send_komentar_notification(self, *args): + komentar = Komentar(deskripsi='dummy deskripsi') + fcmdevice_mock = QuerySet(model=FCMDevice) + fcmdevice_mock_return_value = (1,1,0,0,[]) + fcmdevice_mock.send_message = MagicMock(return_value=fcmdevice_mock_return_value) + formatted_message = 'ariqbasyar menambahkan komentar baru' + + args[0].return_value = 'dummy place_id' + args[1].return_value = 1 + args[2].return_value = 'fasilitas' + args[3].return_value = 'ariqbasyar' + args[4].return_value = formatted_message + args[5].return_value = fcmdevice_mock + + ret = utils.send_komentar_notification(komentar) + + self.assertEqual(ret, fcmdevice_mock_return_value) + args[0].assert_called_once_with(komentar) + args[1].assert_called_once_with(komentar) + args[2].assert_called_once_with(komentar) + args[3].assert_called_once_with(komentar) + args[4].assert_called_once_with('ariqbasyar') + args[5].assert_called_once_with(komentar) + fcmdevice_mock.send_message.assert_called_once_with( + title=formatted_message, + body='"%s"' % komentar.deskripsi, + data={ + 'place_id': 'dummy place_id', + 'id': 1, + 'type': 'fasilitas', + 'message': formatted_message, + } + ) diff --git a/notification/test_views.py b/notification/test_views.py new file mode 100644 index 0000000..14e6afa --- /dev/null +++ b/notification/test_views.py @@ -0,0 +1,203 @@ +from http import HTTPStatus +from unittest.mock import patch +from django.urls import reverse +from django.contrib.auth.models import User + +from fcm_django.models import FCMDevice, AbstractFCMDevice +from fcm_django.api.rest_framework import FCMDeviceSerializer + +from informasi_fasilitas.models import Lokasi, Kegiatan, Fasilitas, Komentar, KomentarKegiatan +from pplbackend.utils import get_client_login_with_user, response_decode +from .test_base import BaseTestNotification + +class TestViewsNotification(BaseTestNotification): + def setUp(self): + super().setUp() + self.get_list_notification_device_url = reverse('notification-list') + self.register_notification_url = self.get_list_notification_device_url + + self.user = User.objects.create_user(username='ariqbasyar') + self.komentar_sender_user = \ + User.objects.create_user(username='ariqbasyar2') + self.lokasi = Lokasi.objects.create() + self.fasilitas = Fasilitas.objects.create(lokasi=self.lokasi, + user=self.user, deskripsi='a') + self.kegiatan = Kegiatan.objects.create(lokasi=self.lokasi, + user=self.user, deskripsi='a') + self.kwargs_kegiatan_id = { + 'place_id': self.lokasi.place_id, + 'kegiatan_id' : self.kegiatan.id, + } + + self.add_komentar_kegiatan_url = \ + reverse('add-komentar-kegiatan', kwargs=self.kwargs_kegiatan_id) + + self.kwargs_add_or_list_komentar = { + 'place_id': self.lokasi.place_id, + 'id': self.fasilitas.id, + } + self.add_komentar_url = \ + reverse('add-komentar', kwargs=self.kwargs_add_or_list_komentar) + + self.komentar_client_sender = get_client_login_with_user(self.komentar_sender_user) + + def test_get_list_devices_from_corresponding_user(self): + resp = self.client.get(self.get_list_notification_device_url) + data = response_decode(resp) + + expected = {'count': 1, 'next': None, 'previous': None, 'results': + FCMDeviceSerializer([self.device], many=True).data} + + self.assertEqual(expected, data) + + user2 = self.create_user_test(user_dict={ + 'username': 'mock username2', + 'email': 'self.mock_user2@test.com', + 'last_name': 'mock last_name2', + }) + device2 = self.create_notification_device_test(user=user2, + notification_device_dict={ + 'registration_id': 'simple token 2', + 'type': 'android' + }) + device3 = self.create_notification_device_test(user=user2, + notification_device_dict={ + 'registration_id': 'simple token 3', + 'type': 'android' + }) + + client2 = get_client_login_with_user(user2) + resp2 = client2.get(self.get_list_notification_device_url) + data2 = response_decode(resp2) + + expected2 = {'count': 2, 'next': None, 'previous': None, 'results': + FCMDeviceSerializer([device2, device3], many=True).data} + + self.assertEqual(expected2, data2) + + def test_can_register_device_and_should_return_its_object(self): + FCMDevice.objects.all().delete() + resp = self.client.post(self.register_notification_url, data={ + 'token': 'simple token', + 'type': 'android', + }) + + obj = FCMDevice.objects.all().first() + serializer = FCMDeviceSerializer(obj) + + expected = serializer.data + data = response_decode(resp) + + self.assertEqual(resp.status_code, HTTPStatus.OK) + self.assertEqual(data, expected) + + def test_can_register_device_type_web_or_android_or_ios(self): + FCMDevice.objects.all().delete() + + device_types = list(map(lambda x: x[0], AbstractFCMDevice.DEVICE_TYPES)) + responses = [] + for _type in device_types: + resp = self.client.post(self.register_notification_url, data={ + 'token': 'simple token', + 'type': _type, + }) + responses.append(resp.status_code) + + self.assertEqual(responses, [HTTPStatus.OK] * len(device_types)) + + def test_cant_register_device_if_device_type_is_not_valid(self): + FCMDevice.objects.all().delete() + _type = 'wearable' + resp = self.client.post(self.register_notification_url, data={ + 'token': 'simple token', + 'type': _type, + }) + + data = response_decode(resp) + expected = {'type': ['"%s" is not a valid choice.' % _type]} + + self.assertEqual(data, expected) + + def test_register_device_return_existing_device_if_duplicate(self): + FCMDevice.objects.all().delete() + self.client.post(self.register_notification_url, data={ + 'token': 'a', + 'type': 'android', + }) + resp = self.client.post(self.register_notification_url, data={ + 'token': 'a', + 'type': 'android', + }) + + obj = FCMDevice.objects.filter(registration_id='a', active=True).first() + serializer = FCMDeviceSerializer(obj) + + expected = serializer.data + data = response_decode(resp) + + self.assertEqual(resp.status_code, HTTPStatus.OK) + self.assertEqual(data, expected) + + def test_cant_register_device_if_registration_id_is_null(self): + FCMDevice.objects.all().delete() + resp = self.client.post(self.register_notification_url, data={ + 'type': 'android', + }) + + data = response_decode(resp) + expected = {'registration_id': ['This field may not be null.']} + + self.assertEqual(data, expected) + + def test_cant_register_device_if_registration_id_is_blank(self): + FCMDevice.objects.all().delete() + resp = self.client.post(self.register_notification_url, data={ + 'token': '', + 'type': 'android', + }) + + data = response_decode(resp) + expected = {'registration_id': ['This field may not be blank.']} + + self.assertEqual(data, expected) + + def test_cant_register_device_if_device_type_is_null(self): + FCMDevice.objects.all().delete() + resp = self.client.post(self.register_notification_url, data={ + 'token': 'simple token', + }) + + data = response_decode(resp) + expected = {'type': ['This field may not be null.']} + + self.assertEqual(data, expected) + + @patch('informasi_fasilitas.views.send_komentar_notification') + def test_post_komentar_should_call_send_komentar_notification(self, mock_notif): + resp = self.komentar_client_sender.post(self.add_komentar_url, data={ + 'deskripsi': 'dummy deskripsi', + }) + data = response_decode(resp) + komentar = Komentar.objects.get(id=data['id']) + + mock_notif.assert_called_once_with(komentar) + + @patch('informasi_fasilitas.views.send_komentar_notification') + def test_failed_to_post_komentar_should_not_call_send_komentar_notification(self, mock_notif): + self.komentar_client_sender.post(self.add_komentar_url, data={}) + mock_notif.assert_not_called() + + @patch('informasi_fasilitas.views_komentar_kegiatan.send_komentar_notification') + def test_post_komentar_kegiatan_should_call_send_komentar_notification(self, mock_notif): + resp = self.komentar_client_sender.post(self.add_komentar_kegiatan_url, data={ + 'deskripsi': 'dummy deskripsi', + }) + data = response_decode(resp) + komentar = KomentarKegiatan.objects.get(id=data['id']) + + mock_notif.assert_called_once_with(komentar) + + @patch('informasi_fasilitas.views_komentar_kegiatan.send_komentar_notification') + def test_failed_to_post_komentar_kegiatan_should_not_call_send_komentar_notification(self, mock_notif): + self.komentar_client_sender.post(self.add_komentar_kegiatan_url, data={}) + mock_notif.assert_not_called() diff --git a/requirements.txt b/requirements.txt index eb157e8..589b95d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,3 +59,8 @@ uritemplate==3.0.1 urllib3==1.26.3 whitenoise==5.2.0 wrapt==1.12.1 +fcm-django==0.3.10 +pyfcm==1.4.9 +django-mock-queries==2.1.6 +mock==4.0.3 +model-bakery==1.1.1 -- GitLab From 7a3f3f31d66ade199000ba7f55d42635e24e0dbe Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Mon, 24 May 2021 12:52:21 +0700 Subject: [PATCH 12/48] [GREEN] implemented notification - register notification with token key - send notification if someone post new komentar or KomentarKegiatan - send notification from admin FCMDevice - send notification from admin Komentar and KomentarKegiatan - add list display for admin Komentar and KomentarKegiatan --- informasi_fasilitas/admin.py | 32 ++++++++++- informasi_fasilitas/models.py | 6 ++ informasi_fasilitas/views.py | 9 +-- .../views_komentar_kegiatan.py | 13 +---- notification/__init__.py | 0 notification/apps.py | 5 ++ notification/migrations/__init__.py | 0 notification/permissions.py | 7 +++ notification/urls.py | 11 ++++ notification/utils.py | 55 +++++++++++++++++++ notification/views.py | 39 +++++++++++++ pplbackend/settings.py | 16 ++++++ pplbackend/urls.py | 1 + 13 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 notification/__init__.py create mode 100644 notification/apps.py create mode 100644 notification/migrations/__init__.py create mode 100644 notification/permissions.py create mode 100644 notification/urls.py create mode 100644 notification/utils.py create mode 100644 notification/views.py diff --git a/informasi_fasilitas/admin.py b/informasi_fasilitas/admin.py index 16f4a84..608afb0 100644 --- a/informasi_fasilitas/admin.py +++ b/informasi_fasilitas/admin.py @@ -1,10 +1,36 @@ -from django.contrib import admin +from django.contrib import admin, messages +from django.utils.translation import gettext_lazy as _ + +from notification.utils import send_komentar_notification, get_target_fcm_device from .models import Lokasi, Fasilitas, Komentar, Kegiatan, KomentarKegiatan, FotoKegiatan + +class KomentarAdmin(admin.ModelAdmin): + list_display = ('__str__', 'deskripsi', 'user', 'notify_to') + actions = ('send_notification',) + + def send_notification(self, request, queryset): + for komentar in queryset: + result = send_komentar_notification(komentar) + if result: + if result['success']: + to = get_target_fcm_device(komentar).first() + msg = _('sent to %s, detail: %r' % (to, result)) + self.message_user(request, msg) + else: + msg = _('failed to send, detail: %r' % (result)) + self.message_user(request, msg, level=messages.WARNING) + + send_notification.short_description = _('Send notification') + + def notify_to(self, obj): + return get_target_fcm_device(obj).first() + notify_to.admin_order_field = 'user' + # Register your models here. admin.site.register(Lokasi) admin.site.register(Fasilitas) -admin.site.register(Komentar) +admin.site.register(Komentar, KomentarAdmin) admin.site.register(Kegiatan) -admin.site.register(KomentarKegiatan) +admin.site.register(KomentarKegiatan, KomentarAdmin) admin.site.register(FotoKegiatan) diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index 7745493..fe0c90d 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -94,6 +94,9 @@ class Komentar(models.Model): date_time = models.DateTimeField(auto_now_add=True) deskripsi = models.TextField() + def __str__(self): + return self.deskripsi + class Likes(models.Model): objects = models.Manager() user = models.ForeignKey(User, on_delete=models.CASCADE) @@ -113,6 +116,9 @@ class KomentarKegiatan(models.Model): deskripsi = models.TextField() created = models.DateTimeField(auto_now_add=True) + def __str__(self): + return self.deskripsi + class FotoKegiatan(models.Model): objects = models.Manager() kegiatan = models.ForeignKey(Kegiatan, related_name="images", on_delete=models.CASCADE) diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index bebbff7..a52ef9c 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -1,18 +1,12 @@ from http import HTTPStatus from django.http import JsonResponse -from django.views.decorators.csrf import csrf_exempt -from django.contrib.auth.models import User -from django.core.exceptions import ObjectDoesNotExist -from django.db.models import F 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.parsers import FileUploadParser -from rest_framework.views import APIView -from rest_framework.generics import ListCreateAPIView from rest_framework import viewsets +from notification.utils import send_komentar_notification from .serializers import LokasiSerializer from .models import Lokasi, Fasilitas, Komentar, Likes, Dislikes @@ -251,6 +245,7 @@ def add_komentar(request, place_id, id): komentar = Komentar.objects.create(fasilitas=fasilitas, user=request.user, deskripsi=deskripsi) + send_komentar_notification(komentar) return JsonResponse({'response': 'komentar added', 'id': komentar.id, "created_date": komentar.date_time.strftime(TIME_FORMAT)}, status=HTTPStatus.CREATED) diff --git a/informasi_fasilitas/views_komentar_kegiatan.py b/informasi_fasilitas/views_komentar_kegiatan.py index eea2872..af6718d 100644 --- a/informasi_fasilitas/views_komentar_kegiatan.py +++ b/informasi_fasilitas/views_komentar_kegiatan.py @@ -1,22 +1,14 @@ from http import HTTPStatus from django.http import JsonResponse -from django.views.decorators.csrf import csrf_exempt -from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist -from django.db.models import F 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.parsers import FileUploadParser -from rest_framework.views import APIView -from rest_framework.generics import ListCreateAPIView -from rest_framework import viewsets +from notification.utils import send_komentar_notification from .serializers import KomentarKegiatanSerializer -from .models import Lokasi, Kegiatan, KomentarKegiatan -from .permissions import UserPermission +from .models import Kegiatan, KomentarKegiatan @api_view(['GET']) @authentication_classes([]) @@ -56,6 +48,7 @@ def add_komentar_kegiatan(request, place_id, kegiatan_id): user=request.user, deskripsi=deskripsi ) + send_komentar_notification(komentar) return JsonResponse({'response': 'komentar kegiatan added', 'id': komentar.id}, status=HTTPStatus.CREATED) except Exception as error: diff --git a/notification/__init__.py b/notification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notification/apps.py b/notification/apps.py new file mode 100644 index 0000000..40b3eb9 --- /dev/null +++ b/notification/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotificationConfig(AppConfig): + name = 'notification' diff --git a/notification/migrations/__init__.py b/notification/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notification/permissions.py b/notification/permissions.py new file mode 100644 index 0000000..566e473 --- /dev/null +++ b/notification/permissions.py @@ -0,0 +1,7 @@ +from rest_framework import permissions + +class NotificationPermission(permissions.BasePermission): + def has_permission(self, request, view): + if view.action in ['list', 'create']: + return request.user.is_authenticated + return False diff --git a/notification/urls.py b/notification/urls.py new file mode 100644 index 0000000..ca1353d --- /dev/null +++ b/notification/urls.py @@ -0,0 +1,11 @@ +from django.urls import path, include +from rest_framework.routers import SimpleRouter + +from . import views + +notification_router = SimpleRouter() +notification_router.register('', views.NotificationViews, basename='notification') + +urlpatterns = [ + path('', include(notification_router.urls)), +] diff --git a/notification/utils.py b/notification/utils.py new file mode 100644 index 0000000..1d7e503 --- /dev/null +++ b/notification/utils.py @@ -0,0 +1,55 @@ +from fcm_django.models import FCMDevice + +from informasi_fasilitas.models import Komentar + +def get_type_informasi_from_komentar(komentar): + if isinstance(komentar, Komentar): + return 'fasilitas' + return 'kegiatan' + +def get_informasi(komentar): + if isinstance(komentar, Komentar): + return komentar.fasilitas + return komentar.kegiatan + +def get_place_id_from_komentar(komentar): + return get_informasi(komentar).lokasi.place_id + +def get_informasi_id_from_komentar(komentar): + return get_informasi(komentar).id + +def get_sender_last_name_from_komentar(komentar): + return str(komentar.user.last_name) + +def get_target_fcm_device(komentar): + return (FCMDevice.objects + .filter(user=get_informasi(komentar).user, active=True) + .exclude(user=komentar.user)) + +def get_formatted_user_name(name, max_length_name=10, replacement_str='...'): + length_replacement = len(replacement_str) + return name if len(name) <= max_length_name else '%s%s' % ( + name[:max_length_name - length_replacement], replacement_str + ) + +def get_formatted_message(sender, **kwargs): + return '%s menambahkan komentar baru' % get_formatted_user_name(sender, **kwargs) + +def send_komentar_notification(komentar): + place_id = get_place_id_from_komentar(komentar) + informasi_id = get_informasi_id_from_komentar(komentar) + informasi_type = get_type_informasi_from_komentar(komentar) + sender = get_sender_last_name_from_komentar(komentar) + formatted_message = get_formatted_message(sender) + target_fcm_device = get_target_fcm_device(komentar) + + return target_fcm_device.send_message( + title=formatted_message, + body='"%s"' % komentar.deskripsi, + data={ + 'place_id': place_id, + 'id': informasi_id, + 'type': informasi_type, + 'message': formatted_message, + } + ) diff --git a/notification/views.py b/notification/views.py new file mode 100644 index 0000000..34fe60a --- /dev/null +++ b/notification/views.py @@ -0,0 +1,39 @@ +from http import HTTPStatus + +from rest_framework.authentication import TokenAuthentication +from rest_framework.response import Response +from rest_framework import viewsets +from fcm_django.api.rest_framework import FCMDeviceViewSet + +from .permissions import NotificationPermission + + +class BaseNotificationViews(viewsets.ModelViewSet): + authentication_classes = [TokenAuthentication] + +class NotificationViews(BaseNotificationViews, FCMDeviceViewSet): + permission_classes = (NotificationPermission,) + + def get_queryset(self): + return self.queryset.filter(user=self.request.user) + + def create(self, request): + data = request.data + fcm_token = data.get('token') + device_type = data.get('type') + existing_fcm_device = self.get_queryset().filter(registration_id=fcm_token) + if existing_fcm_device.exists(): + serializer = self.get_serializer(existing_fcm_device.first()) + return Response(serializer.data, status=HTTPStatus.OK) + serializer = self.get_serializer(data={ + 'name': request.user.last_name, + 'active': request.user.is_active, + 'device_id': '%s - %s' % (request.user.last_name, + fcm_token[:48] if fcm_token else ''), + 'registration_id': fcm_token, + 'type': device_type, + }) + if serializer.is_valid(): + serializer.save(user=request.user) + return Response(serializer.data, status=HTTPStatus.OK) + return Response(serializer.errors, status=HTTPStatus.BAD_REQUEST) diff --git a/pplbackend/settings.py b/pplbackend/settings.py index 1804a87..cb98bad 100644 --- a/pplbackend/settings.py +++ b/pplbackend/settings.py @@ -47,6 +47,7 @@ INSTALLED_APPS = [ 'informasi_fasilitas', 'new_rest_api', 'layanan_khusus', + 'notification', 'rest_auth', 'rest_framework', @@ -58,6 +59,7 @@ INSTALLED_APPS = [ 'allauth.socialaccount.providers.google', 'multiselectfield', 'oauth2_provider', + 'fcm_django', ] MIDDLEWARE = [ @@ -243,3 +245,17 @@ PROXIES = { "https": https_proxy } MAP_API_KEY = os.environ.get('MAP_API_KEY', '') + +FCM_DJANGO_SETTINGS = { + # default: _('FCM Django') + "APP_VERBOSE_NAME": os.getenv('APP_VERBOSE_NAME', 'BisaGo Notification'), + # Your firebase API KEY + "FCM_SERVER_KEY": os.getenv('FCM_SERVER_KEY'), + # true if you want to have only one active device per registered user at a time + # default: False + "ONE_DEVICE_PER_USER": os.getenv('ONE_DEVICE_PER_USER') == 'True', + # devices to which notifications cannot be sent, + # are deleted upon receiving error response from FCM + # default: False + "DELETE_INACTIVE_DEVICES": os.getenv('DELETE_INACTIVE_DEVICES') == 'True', +} diff --git a/pplbackend/urls.py b/pplbackend/urls.py index 835ffdc..eae0351 100644 --- a/pplbackend/urls.py +++ b/pplbackend/urls.py @@ -42,4 +42,5 @@ urlpatterns = [ path('request-token/', views.obtain_auth_token, name='token-request-auth'), #path('api-token-auth/', views.obtain_auth_token, name='api-token-auth'), path('informasi-fasilitas/', include('informasi_fasilitas.urls')), + path('notification/', include('notification.urls')), path('layanan-khusus/', include('layanan_khusus.urls'))] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -- GitLab From 1d1ec8f3d14ad30499150f10c4d2c39feeffd622 Mon Sep 17 00:00:00 2001 From: Christopher Samuel Date: Tue, 25 May 2021 17:21:28 +0700 Subject: [PATCH 13/48] [GREEN] Implemented Agenda Kegiatan functions --- informasi_fasilitas/test_base.py | 21 ++++- informasi_fasilitas/test_views_kegiatan.py | 105 +++++++++++++++++++++ informasi_fasilitas/urls.py | 20 +++- informasi_fasilitas/views_kegiatan.py | 39 ++++++++ 4 files changed, 182 insertions(+), 3 deletions(-) diff --git a/informasi_fasilitas/test_base.py b/informasi_fasilitas/test_base.py index e481d3d..1108940 100644 --- a/informasi_fasilitas/test_base.py +++ b/informasi_fasilitas/test_base.py @@ -48,7 +48,6 @@ class InformasiFasilitasTest(TestCase): 'deskripsi': 'sangat membantu', } - # Waktu mungkin perlu disesuaikan lagi test dan implementasinya mock_kegiatan_test = { 'nama_kegiatan': 'mock kegiatan', 'penyelenggara': 'mock penyelenggara', @@ -59,6 +58,26 @@ class InformasiFasilitasTest(TestCase): 'links': "www.example.com;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', + 'deskripsi': 'sebuah deskripsi', + 'links': "www.example.com;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', + 'deskripsi': 'sebuah deskripsi', + 'links': "www.example.com;www.example.com" + } + mock_komentar_kegiatan_test = { 'deskripsi': 'sangat membantu' } diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index 76dcf03..5802e0d 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -33,6 +33,14 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.kwargs_search_kegiatan_fail = {'query': 'this shouldnt exist',} self.kwargs_kegiatan_id = {'kegiatan_id' : self.kegiatan.lokasi.place_id} + self.kwargs_list_kegiatan_in_order = { + 'start_index': 0, + 'query_limit': 10, + } + self.kwargs_list_kegiatan_in_order_fail = { + 'start_index': 10, + 'query_limit': 20, + } image_path1, image_path2 = ("test_file/test1.jpg", "test_file/test2.jpg") image1 = SimpleUploadedFile("test1.jpg", content=open(image_path1, 'rb').read(), @@ -60,6 +68,18 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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 = \ + reverse('list-kegiatan-by-latest-added', kwargs=self.kwargs_list_kegiatan_in_order) + self.list_kegiatan_by_name_url = \ + reverse('list-kegiatan-by-name', kwargs=self.kwargs_list_kegiatan_in_order) + self.list_kegiatan_by_time_url = \ + reverse('list-kegiatan-by-time', kwargs=self.kwargs_list_kegiatan_in_order) + self.list_kegiatan_by_latest_added_fail_url = \ + reverse('list-kegiatan-by-latest-added', kwargs=self.kwargs_list_kegiatan_in_order_fail) + self.list_kegiatan_by_name_fail_url = \ + reverse('list-kegiatan-by-name', kwargs=self.kwargs_list_kegiatan_in_order_fail) + self.list_kegiatan_by_time_fail_url = \ + reverse('list-kegiatan-by-time', kwargs=self.kwargs_list_kegiatan_in_order_fail) def tearDown(self): try: @@ -240,3 +260,88 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + base_mock_id = str(content['id']) + + response_params = self.mock_kegiatan_name_order_test.copy() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + ordered_name_mock_id = str(content['id']) + + response = self.client.get(self.list_kegiatan_by_name_url) + self.assertEqual(response.status_code, HTTPStatus.OK) + content = json.loads(response.content.decode('utf-8')) + + response_id_in_order = list(content.keys()) + + self.assertEqual(content[base_mock_id]['nama_kegiatan'], + self.kegiatan.nama_kegiatan) + self.assertEqual(content[ordered_name_mock_id]['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) + + def test_can_get_list_kegiatan_by_time_order(self): + Kegiatan.objects.all().delete() + response_params = self.mock_kegiatan_test.copy() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + base_mock_id = str(content['id']) + + response_params = self.mock_kegiatan_name_order_test.copy() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + ordered_time_mock_id = str(content['id']) + + response = self.client.get(self.list_kegiatan_by_time_url) + self.assertEqual(response.status_code, HTTPStatus.OK) + content = json.loads(response.content.decode('utf-8')) + + response_id_in_order = list(content.keys()) + + self.assertEqual(content[base_mock_id]['time_start'], + self.kegiatan.time_start) + self.assertEqual(content[ordered_time_mock_id]['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() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + + response_params = self.mock_kegiatan_test.copy() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + + response = self.client.get(self.list_kegiatan_by_latest_added_url) + self.assertEqual(response.status_code, HTTPStatus.OK) + content = json.loads(response.content.decode('utf-8')) + + 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) diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 6442517..05f598d 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -50,6 +50,24 @@ urlpatterns = [ path('lokasi/list-foto-kegiatan//', views_kegiatan.list_foto_kegiatan, name='list-foto-kegiatan'), + + path('lokasi/list-kegiatan-by-latest-added//', + views_kegiatan.list_kegiatan_by_latest_added, name='list-kegiatan-by-latest-added'), + + path('lokasi/list-kegiatan-by-latest-added/', + views_kegiatan.list_kegiatan_by_latest_added, name='list-kegiatan-by-latest-added'), + + path('lokasi/list-kegiatan-by-name//', + views_kegiatan.list_kegiatan_by_name, name='list-kegiatan-by-name'), + + path('lokasi/list-kegiatan-by-name/', + views_kegiatan.list_kegiatan_by_name, name='list-kegiatan-by-name'), + + path('lokasi/list-kegiatan-by-time//', + views_kegiatan.list_kegiatan_by_time, name='list-kegiatan-by-time'), + + path('lokasi/list-kegiatan-by-time/', + views_kegiatan.list_kegiatan_by_time, name='list-kegiatan-by-time'), path('lokasi/get-komentar-kegiatan///', views_komentar_kegiatan.get_komentar_kegiatan, name='get-komentar-kegiatan'), @@ -62,6 +80,4 @@ urlpatterns = [ path('lokasi/delete-komentar-kegiatan///', views_komentar_kegiatan.delete_komentar_kegiatan, name='delete-komentar-kegiatan'), - - ] diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 3228f1a..0c8094c 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -109,6 +109,45 @@ def search_kegiatan(request, query): new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} return JsonResponse(new_dict, status=HTTPStatus.OK) +@api_view(['GET']) +@authentication_classes([]) +@permission_classes([]) +def list_kegiatan_by_name(request, start_index, query_limit=9): + time_now = timezone.now() + queryset = Kegiatan.objects.filter(time_end__gte=time_now).order_by('nama_kegiatan') + queryset = queryset[start_index:start_index+query_limit] + if not queryset.exists(): + raise NotFound(detail="No Kegiatan available") + serializer = KegiatanSerializer(queryset, many=True) + new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} + return JsonResponse(new_dict, status=HTTPStatus.OK) + +@api_view(['GET']) +@authentication_classes([]) +@permission_classes([]) +def list_kegiatan_by_time(request, start_index, query_limit=9): + time_now = timezone.now() + queryset = Kegiatan.objects.filter(time_end__gte=time_now).order_by('time_start') + queryset = queryset[start_index:start_index+query_limit] + if not queryset.exists(): + raise NotFound(detail="No Kegiatan available") + serializer = KegiatanSerializer(queryset, many=True) + new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} + return JsonResponse(new_dict, status=HTTPStatus.OK) + +@api_view(['GET']) +@authentication_classes([]) +@permission_classes([]) +def list_kegiatan_by_latest_added(request, start_index, query_limit=9): + time_now = timezone.now() + queryset = Kegiatan.objects.filter(time_end__gte=time_now).order_by('-id') + queryset = queryset[start_index:start_index+query_limit] + if not queryset.exists(): + raise NotFound(detail="No Kegiatan available") + serializer = KegiatanSerializer(queryset, many=True) + new_dict = {item['id']: _clean_json_kegiatan(dict(item)) for item in serializer.data} + return JsonResponse(new_dict, status=HTTPStatus.OK) + def _clean_json_kegiatan(kegiatan): kegiatan.pop("user") kegiatan.pop("lokasi") -- GitLab From c6477b7ab1e2a49d2c2be6abdf2350e8c35a5226 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Wed, 26 May 2021 22:53:50 +0700 Subject: [PATCH 14/48] [RED] tests for BisaGoUser (seen and organisasi_komunitas field) and refactors - test seen field (can be seen by him/herself but not other when seen is False - test organisasi_komunitas field not required with default '-' - test serializers for register, detail, update and activate user/bisagouser - test send email, fix activation url on message is not properly configured - --- informasi_fasilitas/test_admin.py | 3 +- informasi_fasilitas/test_views_kegiatan.py | 26 +- informasi_fasilitas/test_views_komentar.py | 4 +- layanan_khusus/tests.py | 22 +- new_rest_api/test_permissions.py | 103 +++++++ new_rest_api/test_tokens.py | 20 ++ new_rest_api/test_utils.py | 56 ++++ new_rest_api/test_views.py | 153 +++++++++++ new_rest_api/tests.py | 199 -------------- registrasi/test_models.py | 14 + registrasi/test_serializers.py | 297 +++++++++++++++++++++ 11 files changed, 669 insertions(+), 228 deletions(-) create mode 100644 new_rest_api/test_permissions.py create mode 100644 new_rest_api/test_tokens.py create mode 100644 new_rest_api/test_utils.py create mode 100644 new_rest_api/test_views.py delete mode 100644 new_rest_api/tests.py create mode 100644 registrasi/test_models.py create mode 100644 registrasi/test_serializers.py diff --git a/informasi_fasilitas/test_admin.py b/informasi_fasilitas/test_admin.py index 335a601..9bf6e1f 100644 --- a/informasi_fasilitas/test_admin.py +++ b/informasi_fasilitas/test_admin.py @@ -37,8 +37,7 @@ class TestAdmin(TestCase): def test_list_display(self): list_display = self.komentar_admin.get_list_display(request) - self.assertEqual(list_display, ('__str__', 'deskripsi', 'user', - 'notify_to')) + self.assertEqual(list_display, ('__str__', 'user', 'notify_to')) def test_list_actions(self): list_action = self.komentar_admin.get_action_choices(request) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index 76dcf03..d8545b1 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -1,18 +1,20 @@ +from unittest.mock import patch +from datetime import timedelta +from http import HTTPStatus + import json import shutil import os -from datetime import timedelta -from http import HTTPStatus + from django.test import Client from django.urls import reverse from django.test import override_settings from django.utils import timezone from django.core.files.uploadedfile import SimpleUploadedFile from django.conf import settings -from unittest.mock import patch + from .test_base import InformasiFasilitasViewTest -from .models import Lokasi, Kegiatan, FotoKegiatan -from pplbackend.utils import response_decode +from .models import Kegiatan, FotoKegiatan class KegiatanRelatedViewTest(InformasiFasilitasViewTest): @@ -79,7 +81,8 @@ 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', + 'creator_email': self.mock_user_test['email'] }) self.assertEqual(response.status_code, HTTPStatus.CREATED) self.assertDictEqual(data, expected_json) @@ -114,6 +117,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, + 'creator_email': self.kegiatan.user.email, 'nama_kegiatan' : self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, @@ -133,6 +137,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, + 'creator_email': self.kegiatan.user.email, 'nama_kegiatan' : self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, @@ -151,6 +156,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, + 'creator_email': self.kegiatan.user.email, 'nama_kegiatan' : self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, @@ -179,7 +185,8 @@ 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', + 'creator_email': self.mock_user_test['email'] }) send_data.pop("images") expected_json.update(send_data) @@ -216,7 +223,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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) @@ -226,6 +233,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, + 'creator_email': self.kegiatan.user.email, 'nama_kegiatan' : self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, @@ -236,7 +244,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): }, } 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) diff --git a/informasi_fasilitas/test_views_komentar.py b/informasi_fasilitas/test_views_komentar.py index b81ba1f..d2b05eb 100644 --- a/informasi_fasilitas/test_views_komentar.py +++ b/informasi_fasilitas/test_views_komentar.py @@ -1,10 +1,9 @@ -import json from http import HTTPStatus from django.urls import reverse +from pplbackend.utils import response_decode from .test_base import InformasiFasilitasViewTest from .models import Komentar -from pplbackend.utils import response_decode from .views import TIME_FORMAT @@ -48,6 +47,7 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): 'id': komentar.id, 'deskripsi': komentar.deskripsi, 'creator': komentar.user.last_name, + 'creator_email': komentar.user.email, 'date_time': komentar.date_time.strftime(TIME_FORMAT), } } diff --git a/layanan_khusus/tests.py b/layanan_khusus/tests.py index 1ec1661..a321413 100644 --- a/layanan_khusus/tests.py +++ b/layanan_khusus/tests.py @@ -5,9 +5,12 @@ from django.conf import settings from django.db.utils import IntegrityError from django.contrib.auth.models import User from django.urls import path, include, reverse + +import django + +from pplbackend.utils import get_client_login_with_user from .models import Sekolah, Penyandang, Komunitas from .serializers import SekolahSerializer, KomunitasSerializer -import django ID = 'id' NAME = 'name' @@ -118,21 +121,8 @@ def penyandang_setup(): def auth_setup(): email = 'mock_user@email.com' - passcode = 'pass12345' - Client().post('/api/register/', { - 'name': 'name', - 'email': email, - 'phone_number': 000000000, - 'password': passcode - }) - test_user = User.objects.get(username=email) - test_user.is_active = True - test_user.save() - token_response = Client().post('/api-token-auth/', - {'username': email, 'password': passcode}) - content = json.loads(token_response.content.decode('utf-8')) - token = content['token'] - return Client(HTTP_AUTHORIZATION='token '+token) + user = User.objects.create_user(username=email) + return get_client_login_with_user(user) class LayananKhususModelTest(TestCase): diff --git a/new_rest_api/test_permissions.py b/new_rest_api/test_permissions.py new file mode 100644 index 0000000..6569caa --- /dev/null +++ b/new_rest_api/test_permissions.py @@ -0,0 +1,103 @@ +from django.test import TestCase +from .permissions import UserViewPermission + +class MockUser: + def __init__(self, id=1, is_authenticated=False): + self.id = id + self.is_authenticated = is_authenticated + +class MockUserAuthenticated(MockUser): + def __init__(self, *args, **kwargs): + super().__init__(is_authenticated=True, *args, **kwargs) + +class MockUserNotAuthenticated(MockUser): + def __init__(self, *args, **kwargs): + super().__init__(is_authenticated=False, *args, **kwargs) + +class MockRequest: + def __init__(self, user): + self.user = user + +class MockView: + def __init__(self, action): + self.action = action + +class MockObject: + def __init__(self, user): + self.user = user + +class TestPermission(TestCase): + def setUp(self): + self.user_view_permission = UserViewPermission() + + self.authenticated_request = MockRequest(MockUserAuthenticated()) + self.not_authenticated_request = MockRequest(MockUserNotAuthenticated()) + + self.view_list = MockView('list') + self.view_create = MockView('register') + self.view_retrieve = MockView('retrieve') + self.view_update = MockView('update') + self.view_delete = MockView('delete') + + # has permission tests + def test_permission_authenticated_cant_list(self): + ret = self.user_view_permission\ + .has_permission(self.authenticated_request, self.view_list) + self.assertFalse(ret) + + def test_permission_not_authenticated_cant_list(self): + ret = self.user_view_permission\ + .has_permission(self.not_authenticated_request, self.view_list) + self.assertFalse(ret) + + def test_permission_authenticated_can_create(self): + ret = self.user_view_permission\ + .has_permission(self.authenticated_request, self.view_create) + self.assertTrue(ret) + + def test_permission_not_authenticated_can_create(self): + ret = self.user_view_permission\ + .has_permission(self.not_authenticated_request, self.view_create) + self.assertTrue(ret) + + def test_permission_authenticated_can_update(self): + ret = self.user_view_permission\ + .has_permission(self.authenticated_request, self.view_update) + self.assertTrue(ret) + + def test_permission_not_authenticated_cant_update(self): + ret = self.user_view_permission\ + .has_permission(self.not_authenticated_request, self.view_update) + self.assertFalse(ret) + + def test_permission_authenticated_cant_delete(self): + ret = self.user_view_permission\ + .has_permission(self.authenticated_request, self.view_delete) + self.assertFalse(ret) + + def test_permission_not_authenticated_cant_delete(self): + ret = self.user_view_permission\ + .has_permission(self.not_authenticated_request, self.view_delete) + self.assertFalse(ret) + + # has object permission tests + def test_object_permission_can_update_same_user(self): + user = MockUser(id=self.authenticated_request.user.id) + obj = MockObject(user) + ret = self.user_view_permission\ + .has_object_permission(self.authenticated_request, self.view_update, obj) + self.assertTrue(ret) + + def test_object_permission_cant_update_difference_user(self): + user = MockUser(id=self.authenticated_request.user.id + 1) + obj = MockObject(user) + ret = self.user_view_permission\ + .has_object_permission(self.authenticated_request, self.view_update, obj) + self.assertFalse(ret) + + def test_object_permission_other_action(self): + user = MockUser(id=self.authenticated_request.user.id + 1) + obj = MockObject(user) + ret = self.user_view_permission\ + .has_object_permission(self.authenticated_request, self.view_list, obj) + self.assertTrue(ret) diff --git a/new_rest_api/test_tokens.py b/new_rest_api/test_tokens.py new file mode 100644 index 0000000..2e3d5cd --- /dev/null +++ b/new_rest_api/test_tokens.py @@ -0,0 +1,20 @@ +from datetime import datetime + +from django.utils import six +from django.test import TestCase + +from .tokens import account_activation_token + +class MockUser: + pk = 1 + is_active = False + + +class TestTokenGenerator(TestCase): + def test_make_hash_value(self): + user = MockUser() + now = datetime.now() + ret = account_activation_token._make_hash_value(user, now) + expected = (six.text_type(user.pk) + six.text_type(now) + + six.text_type(user.is_active)) + self.assertEqual(ret, expected) diff --git a/new_rest_api/test_utils.py b/new_rest_api/test_utils.py new file mode 100644 index 0000000..1e3d93a --- /dev/null +++ b/new_rest_api/test_utils.py @@ -0,0 +1,56 @@ +from unittest.mock import MagicMock, patch + +from django.test import TestCase +from django.test.client import RequestFactory + +from .utils import send_activation_email + +class MockUser: + pk = 1 + email = 'dummy@email.com' + username = email + last_name = 'dummy the boi' + +class TestUtilsUser(TestCase): + mock_activation_endpoint = '/api/user/activate/someuid/some-token/' + mock_email_message = MagicMock() + mock_user = MockUser() + mock_request = RequestFactory().get('') + mock_subject = 'dummy subject' + mock_token = 'some-token' + mock_uid = 'someuid' + mock_bytes = b'some bytes' + + @patch('new_rest_api.utils.EmailMessage', return_value=mock_email_message) + @patch('new_rest_api.utils.reverse', return_value=mock_activation_endpoint) + @patch('new_rest_api.utils.account_activation_token.make_token', return_value=mock_token) + @patch('new_rest_api.utils.urlsafe_base64_encode', return_value=mock_uid) + @patch('new_rest_api.utils.force_bytes', return_value=mock_bytes) + def test_send_activation_email(self, mock_force_bytes, mock_urlsafe, + mock_make_token, mock_reverse, mock_init_email): + + send_activation_email(self.mock_user, self.mock_request, subject=self.mock_subject) + + absolute_uri = self.mock_request.build_absolute_uri(self.mock_activation_endpoint) + + message = f""" +Hai {self.mock_user.last_name}, +Selamat datang di aplikasi bisaGO. +Sebelum anda bisa menggunakan akun anda, silahkan melakukan aktivasi dengan meng-klik link di bawah ini. +{absolute_uri} + +Terima kasih dan selamat menggunakan bisaGO. + + +Salam, +bisaGO dev Team +""" + + mock_force_bytes.assert_called_once_with(self.mock_user.pk) + mock_urlsafe.assert_called_once_with(self.mock_bytes) + mock_make_token.assert_called_once_with(self.mock_user) + mock_reverse.assert_called_once_with('user-activate', + kwargs={'uidb64': self.mock_uid, 'token': self.mock_token}) + mock_init_email.assert_called_once_with(self.mock_subject, message, + to=[self.mock_user.email]) + self.mock_email_message.send.assert_called_once() diff --git a/new_rest_api/test_views.py b/new_rest_api/test_views.py new file mode 100644 index 0000000..4fa9308 --- /dev/null +++ b/new_rest_api/test_views.py @@ -0,0 +1,153 @@ +from http import HTTPStatus as status +from unittest.mock import patch, MagicMock + +from django.contrib.auth.models import User +from django.core.files.uploadedfile import SimpleUploadedFile +from django.urls import reverse +from django.test import TestCase + +from pplbackend.utils import response_decode + + +class TestUserViews(TestCase): + mock_email_message = MagicMock() + mock_token = 'some-token%s' % ('n'*27) + mock_uid = 'someuid' + + def setUp(self): + self.user = User.objects.create_user(username='test@gmail.com') + + self.register_url = reverse('user-register') + self.detail_url = reverse('user-detail', + kwargs={'user__username':'test@gmail.com'}) + self.update_url = self.detail_url + self.image = SimpleUploadedFile("test1.jpg", + content=open("test_file/test1.jpg", 'rb').read(), content_type='image/jpeg') + self.register_data = { + 'name': 'Dummy the boi', + 'email': 'dummy@test.com', + 'password': 'password', + 'phone_number': '000011112222', + 'tanggal_lahir': '2000-01-01', + 'jenis_kelamin': 'laki-laki', + 'foto': self.image, + } + + @patch('registrasi.serializers.send_activation_email') + def test_register_and_send_activation_email_is_called_and_user_inactive(self, + mock_ativation_email): + + resp = self.client.post(self.register_url, data=self.register_data) + data = response_decode(resp) + expected = { + 'response': 'User registered, check activation link on your email', + 'email': 'dummy@test.com', + 'name': 'Dummy the boi', + } + + user = User.objects.get(username='dummy@test.com') + + mock_ativation_email.assert_called_once() + self.assertEqual(data, expected) + self.assertFalse(user.is_active) + self.assertEqual(resp.status_code, status.CREATED) + + def test_register_invalid_required_fields(self): + resp = self.client.post(self.register_url, data={}) + data = response_decode(resp) + expected = { + 'name': ['This field is required.'], + 'email': ['This field is required.'], + 'password': ['This field is required.'], + 'tanggal_lahir': ['This field is required.'], + 'jenis_kelamin': ['This field is required.'], + 'phone_number': ['This field is required.'] + } + + self.assertEqual(data, expected) + self.assertEqual(resp.status_code, status.BAD_REQUEST) + + @patch('new_rest_api.views.account_activation_token.check_token', return_value=True) + @patch('new_rest_api.views.force_text') + @patch('new_rest_api.views.urlsafe_base64_decode', return_value='someb64') + @patch('new_rest_api.utils.EmailMessage', return_value=mock_email_message) + @patch('new_rest_api.utils.account_activation_token.make_token', return_value=mock_token) + @patch('new_rest_api.utils.urlsafe_base64_encode', return_value=mock_uid) + def test_send_activation_email_and_activate(self, *args): + self.client.post(self.register_url, data=self.register_data) + + user = User.objects.get(username=self.register_data['email']) + self.assertFalse(user.is_active) + args[4].return_value = user.pk + + activation_url = reverse('user-activate', kwargs={ + 'token': self.mock_token, + 'uidb64': self.mock_uid, + }) + + + resp = self.client.get(activation_url) + data = response_decode(resp) + expected = {'response': 'User activated'} + + user.refresh_from_db() + self.assertTrue(user.is_active) + self.assertEqual(resp.status_code, status.OK) + self.assertEqual(data, expected) + + @patch('new_rest_api.views.account_activation_token.check_token', return_value=True) + @patch('new_rest_api.views.force_text') + @patch('new_rest_api.views.urlsafe_base64_decode', return_value='someb64') + @patch('new_rest_api.utils.EmailMessage', return_value=mock_email_message) + @patch('new_rest_api.utils.account_activation_token.make_token', return_value=mock_token) + @patch('new_rest_api.utils.urlsafe_base64_encode', return_value=mock_uid) + def test_send_activation_email_and_activate_user_doesnt_exists(self, *args): + self.client.post(self.register_url, data=self.register_data) + + user = User.objects.get(username=self.register_data['email']) + self.assertFalse(user.is_active) + user_pk = user.pk + 1 + args[4].return_value = user_pk + + activation_url = reverse('user-activate', kwargs={ + 'token': self.mock_token, + 'uidb64': self.mock_uid, + }) + + resp = self.client.get(activation_url) + data = response_decode(resp) + expected = {'response': 'error'} + + user.refresh_from_db() + self.assertFalse(user.is_active) + self.assertEqual(resp.status_code, status.BAD_REQUEST) + self.assertEqual(data, expected) + + @patch('new_rest_api.views.account_activation_token.check_token', return_value=False) + @patch('new_rest_api.views.force_text') + @patch('new_rest_api.views.urlsafe_base64_decode', return_value='someb64') + @patch('new_rest_api.utils.EmailMessage', return_value=mock_email_message) + @patch('new_rest_api.utils.account_activation_token.make_token', return_value=mock_token) + @patch('new_rest_api.utils.urlsafe_base64_encode', return_value=mock_uid) + def test_send_activation_email_and_activate_token_doesnt_exists(self, *args): + self.client.post(self.register_url, data=self.register_data) + + user = User.objects.get(username=self.register_data['email']) + self.assertFalse(user.is_active) + + other_user = User.objects.create_user(username='other dummy') + args[4].return_value = other_user.pk + + activation_url = reverse('user-activate', kwargs={ + 'token': self.mock_token, + 'uidb64': self.mock_uid, + }) + + resp = self.client.get(activation_url) + data = response_decode(resp) + expected = {'response': 'error'} + + user.refresh_from_db() + self.assertFalse(user.is_active) + self.assertEqual(resp.status_code, status.BAD_REQUEST) + self.assertEqual(data, expected) diff --git a/new_rest_api/tests.py b/new_rest_api/tests.py deleted file mode 100644 index e37a1ab..0000000 --- a/new_rest_api/tests.py +++ /dev/null @@ -1,199 +0,0 @@ -# Create your tests here. -import json -from rest_framework.test import APITestCase, URLPatternsTestCase -from rest_framework import status -from django.urls import include, path, reverse -from django.utils.http import urlsafe_base64_encode -from django.utils.encoding import force_bytes -from registrasi.models import BisaGoUser -from .tokens import account_activation_token -import io -import requests - -MOCK_USER = {'name': 'Astraykai', - 'email':'astraykai@gmail.com', - 'password':'chingchenghanji', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu'} - -class UserTests(APITestCase): - urlpatterns = [ - path('api/', include('new_rest_api.urls')), - ] - - def setUp(self): - url = reverse('create-user') - data = MOCK_USER - self.client.post(url, data) - - def test_create_user(self): - """ - Ensure we can create a new account object. - """ - url = reverse('create-user') - data = {'name': 'Astray', - 'email':'astrayyahoo@gmail.com', - 'password':'chingchenghanji', - 'phone_number':'08989221856', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'Yuhu', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu', - 'is_active': True} - response = self.client.post(url, data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(BisaGoUser.objects.count(), 2) - user = BisaGoUser.objects.get(phone_number='08989221856').user - self.assertEqual(user.last_name, 'Astray') - - url = reverse('user-details', kwargs={'email':'astrayyahoo@gmail.com'}) - response = self.client.get(url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - json_test = json.loads(response.content) - self.assertEqual(len(json_test), 12) #JSON Attribute - - def test_account_details(self): - url = reverse('user-details', kwargs={'email':'astraykai@gmail.com'}) - response = self.client.get(url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - json_test = json.loads(response.content) - self.assertEqual(len(json_test), 12) #JSON Attribute - - def test_account_list(self): - url = reverse('user-list') - response = self.client.get(url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - json_test = json.loads(response.content) - self.assertEqual(len(json_test), 1) - - def test_incomplete_create_user(self): - url = reverse('create-user') - data = {'email':'astrayyahoo@gmail.com', - 'password':'chingchenghanji', - 'is_active': True} - response = self.client.post(url, data) - self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) - json_response = json.loads(response.content) - self.assertEqual(json_response.get('response'), 'bad request. \'name\' key needed') - - def test_user_already_exist(self): - url = reverse('create-user') - data = MOCK_USER - data["is_active"] = True - response = self.client.post(url, data) - self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) - json_response = json.loads(response.content) - self.assertEqual(json_response['response'], 'User is already exist') - - def test_invalid_request(self): - url = reverse('user-list') - data = MOCK_USER - data["is_active"] = True - response = self.client.post(url, data) - self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) - json.loads(response.content) - - def test_account_login(self): - pass - - def test_without_verification(self): - url = reverse('api-token-auth') - data = {'username': 'astraykai@gmail.com', - 'password':'chingchenghanji'} - response = self.client.post(url, data) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_activation_function(self): - user = BisaGoUser.objects.get(phone_number='089892218567').user - uid = urlsafe_base64_encode(force_bytes(user.pk)) - token = account_activation_token.make_token(user) - url = reverse('activate', kwargs={'uidb64' : uid, 'token' : token}) - response = self.client.get(url) - json_response = json.loads(response.content) - self.assertEqual(json_response['response'], 'User activated') - - def test_update_existing_user(self): - ''' - Ensure that when valid data is sent - It will successfully to update the user - ''' - url = reverse('update-user') - data = MOCK_USER - response = self.client.post(url, data) - json_response = json.loads(response.content) - self.assertEqual(json_response['response'], 'User updated') - - - def test_update_nonexistince_user(self): - ''' - Ensure that when user does not exist in the database - It will failed to update the user - ''' - url = reverse('update-user') - data = {'name': 'Astraykai', - 'email':'astraykaii@gmail.com', - 'phone_number':'089892218567', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu'} - response = self.client.post(url, data) - json_response = json.loads(response.content) - self.assertEqual(json_response['response'], 'User not found') - - - def test_update_user_with_invalid_data(self): - ''' - Ensure that when invalid data is sent - It will failed to update the user - ''' - url = reverse('update-user') - data = MOCK_USER - data["phone_number"] = "" - response = self.client.post(url, data) - json_response = json.loads(response.content) - self.assertEqual(json_response['response'], 'Internal server error') - - def test_upload_foto_with_invalid_image(self): - ''' - Ensure that profile picture will not be updated if invalid image is sent - ''' - - url = reverse('update-user') - data = MOCK_USER - data["foto"] = (io.BytesIO(b"this is a test"), 'test.pdf') - response = self.client.post(url, data, headers={'Content-Type': "multipart/form-data"}) - json_response = json.loads(response.content) - self.assertEqual(json_response['response'], 'File is not image type') - - def test_all_user_information_can_be_seen_after_register(self): - ''' - Ensure that user information can be seen by others after registration - ''' - - url = reverse('create-user') - data = {'name': 'Astray', - 'email':'astrayyahoo@gmail.com', - 'password':'chingchenghanji', - 'phone_number':'08989221856', - 'tanggal_lahir':'1990-05-05', - 'jenis_kelamin':'Laki-laki', - 'disabilitas':'Yuhu', - 'pekerjaan':'Mahasiswa', - 'alamat':'Alamat Palsu', - 'is_active': True} - response = self.client.post(url, data) - - url = reverse('user-details', kwargs={'email':'astrayyahoo@gmail.com'}) - response = self.client.get(url, format='json') - json_test = json.loads(response.content) - self.assertEqual(json_test['seen'], True) - -class InfoTests(APITestCase, URLPatternsTestCase): - pass \ No newline at end of file diff --git a/registrasi/test_models.py b/registrasi/test_models.py new file mode 100644 index 0000000..ec7a069 --- /dev/null +++ b/registrasi/test_models.py @@ -0,0 +1,14 @@ +from django.contrib.auth.models import User +from django.test import TestCase + +from .models import BisaGoUser + + +class TestModelsBisaGoUser(TestCase): + def test_str(self): + user = User.objects.create( + username='dummy@test.com', last_name='dummy lastname') + bisagouser = BisaGoUser.objects.create(user=user, + phone_number='000011112222') + + self.assertEqual(str(bisagouser), user.username) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py new file mode 100644 index 0000000..1c56031 --- /dev/null +++ b/registrasi/test_serializers.py @@ -0,0 +1,297 @@ +from unittest.mock import patch + +from django.contrib.auth.models import User +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase + +from rest_framework import serializers + +from .serializers import BisaGoUserSerializers, RegisterUserSerializer +from .models import BisaGoUser + + +class MockUser: + def __init__(self, id=1): + self.id = id + + +class MockRequest: + def __init__(self, user_id=1): + self.user = MockUser(id=user_id) + + def build_absolute_uri(self, path): + return 'http:/dummy%s' % path + + +class BaseTestSerializer(TestCase): + mock_user = { + 'username': 'dummy username', + 'last_name': 'dummy lastname', + 'email': 'test@email.com', + } + mock_bisagouser = { + 'phone_number': '081208120812', + 'alamat': 'Dummy St' + } + + def setUp(self): + self.image = SimpleUploadedFile("test1.jpg", + content=open("test_file/test1.jpg", 'rb').read(), content_type='image/jpeg') + +class TestBisaGoUserSerializer(BaseTestSerializer): + def test_meta_model(self): + self.assertEqual(BisaGoUserSerializers.Meta.model, BisaGoUser) + + def test_meta_fields(self): + self.assertSequenceEqual(BisaGoUserSerializers.Meta.fields, ( + 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'name', 'email', + 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', + 'organisasi_komunitas', + )) + + def test_meta_hidden_fields(self): + self.assertEqual(BisaGoUserSerializers.Meta.hidden_fields, + ('phone_number', 'email', 'alamat')) + + def test_meta_hidden_replacement_char(self): + self.assertEqual(BisaGoUserSerializers.Meta.hidden_replacement_char, '-') + + def test_meta_update_fields_mapper(self): + self.assertDictEqual(BisaGoUserSerializers.Meta.update_fields_mapper, { + 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', + 'jenis_kelamin': 'jenis_kelamin', 'tanggal_lahir': 'tanggal_lahir', + 'disabilitas': 'disabilitas', 'pekerjaan': 'pekerjaan', + 'foto': 'foto', 'organisasi_komunitas': 'organisasi_komunitas' + }) + + def test_meta_update_user_fields_mapper(self): + self.assertEqual(BisaGoUserSerializers.Meta.update_user_fields_mapper, { + 'last_name': 'last_name', + }) + + def test_can_see_hidden_fields_obj_seen_is_true_from_others(self): + user = User.objects.create_user(**self.mock_user) + bisagouser = BisaGoUser.objects.create(user=user, **self.mock_bisagouser) + + user_id = 99 + assert user.id != user_id + request = MockRequest(user_id=user_id) + bisagouser = BisaGoUserSerializers(bisagouser, + context={'request':request}) + + expected = { + 'phone_number': '081208120812', 'tanggal_lahir': '2021-05-26', + 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', + 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': True, + 'username': 'dummy username', 'name': 'dummy lastname', + 'email': 'test@email.com', 'alamat': 'Dummy St', + 'organisasi_komunitas': '-' + } + + self.assertEqual(bisagouser.data, expected) + + def test_can_see_hidden_fields_obj_seen_is_true_from_itself(self): + user = User.objects.create_user(**self.mock_user) + bisagouser = BisaGoUser.objects.create(user=user, **self.mock_bisagouser) + + request = MockRequest(user_id=user.id) + bisagouser = BisaGoUserSerializers(bisagouser, + context={'request':request}) + + expected = { + 'phone_number': '081208120812', 'tanggal_lahir': '2021-05-26', + 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', + 'pekerjaan': '-', 'alamat': 'Dummy St', 'foto': None, 'seen': True, + 'username': 'dummy username', 'name': 'dummy lastname', + 'email': 'test@email.com', 'organisasi_komunitas': '-', + } + + self.assertEqual(bisagouser.data, expected) + + def test_cant_see_hidden_fields_obj_seen_is_false_from_other(self): + user = User.objects.create_user(**self.mock_user) + bisagouser = BisaGoUser.objects.create(user=user, + **self.mock_bisagouser, seen=False) + + user_id = 99 + assert user_id != user.id + request = MockRequest(user_id=user_id) + bisagouser = BisaGoUserSerializers(bisagouser, + context={'request':request}) + + replaced = BisaGoUserSerializers.Meta.hidden_replacement_char + expected = { + 'phone_number': replaced, 'tanggal_lahir': '2021-05-26', + 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', + 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': False, + 'username': 'dummy username', 'name': 'dummy lastname', + 'email': replaced, 'alamat': replaced, + 'organisasi_komunitas': '-' + } + + self.assertEqual(bisagouser.data, expected) + + def test_can_update(self): + user = User.objects.create_user(**self.mock_user) + bisagouser = BisaGoUser.objects.create(user=user, + **self.mock_bisagouser) + + update = { + 'name': 'another dummy name', 'phone_number': '99999999', + 'seen': False, 'alamat': 'dif dummy st', 'foto': self.image, + 'jenis_kelamin': 'laki-laki', 'tanggal_lahir': '2000-10-02', + 'disabilitas': 'tanpa batas', 'pekerjaan': 'makan', + 'organisasi_komunitas': 'komunitas tanpa batas' + } + request = MockRequest(user_id=user.id) + + serializer = BisaGoUserSerializers(bisagouser, data=update, + context={'request':request}) + serializer.is_valid(raise_exception=True) + serializer.save() + + expected = { + 'phone_number': '99999999', 'tanggal_lahir': '2000-10-02', + 'jenis_kelamin': 'laki-laki', 'disabilitas': 'tanpa batas', + 'pekerjaan': 'makan', 'alamat': 'dif dummy st', 'seen': False, + 'username': 'dummy username', 'name': 'another dummy name', + 'email': 'test@email.com', + 'organisasi_komunitas': 'komunitas tanpa batas' + } + + data = serializer.data + data.pop('foto') + + self.assertEqual(data, expected) + + +class TestRegisterUserSerializer(BaseTestSerializer): + def test_meta_model(self): + self.assertEqual(RegisterUserSerializer.Meta.model, User) + + def test_meta_fields(self): + self.assertSequenceEqual(RegisterUserSerializer.Meta.fields, ( + 'name', 'email', 'password', 'tanggal_lahir', 'jenis_kelamin', + 'disabilitas', 'pekerjaan', 'alamat', 'phone_number', 'foto', + 'organisasi_komunitas', + )) + + def test_meta_user_fields_mapper(self): + self.assertEqual(RegisterUserSerializer.Meta.user_fields_mapper, { + 'username': 'email', + 'email': 'email', + 'password': 'password', + 'last_name': 'name', + }) + + def test_meta_bisago_fields_mapper(self): + self.assertEqual(RegisterUserSerializer.Meta.bisago_fields_mapper, { + 'tanggal_lahir': 'tanggal_lahir', + 'jenis_kelamin': 'jenis_kelamin', + 'phone_number': 'phone_number', + 'disabilitas': 'disabilitas', + 'organisasi_komunitas': 'organisasi_komunitas', + 'pekerjaan': 'pekerjaan', + 'alamat': 'alamat', + 'foto': 'foto', + 'user': 'user', + }) + + def test_validate_email_already_exists(self): + user = User.objects.create(username='dummy@test.com') + + serializer = RegisterUserSerializer() + + with self.assertRaises(serializers.ValidationError) as err: + serializer.validate_email(user.username) + + self.assertEqual(err.expected, serializers.ValidationError) + self.assertEqual('email already exists.', err.exception.args[0]) + + def test_validate_email_doesnt_exists(self): + User.objects.create(username='dummy@test.com') + + serializer = RegisterUserSerializer() + + email = serializer.validate_email('another@test.com') + + self.assertEqual(email, 'another@test.com') + + def test_validate_phone_number_doesnt_match_regex(self): + serializer = RegisterUserSerializer() + + with self.assertRaises(serializers.ValidationError) as err: + serializer.validate_phone_number('0000') + + self.assertEqual(err.expected, serializers.ValidationError) + self.assertEqual('invalid phone number.', err.exception.args[0]) + + def test_validate_phone_number_already_exists(self): + user = User.objects.create_user(username='dummy username') + bisagouser = BisaGoUser.objects.create(user=user, + phone_number='000088881111') + serializer = RegisterUserSerializer() + + with self.assertRaises(serializers.ValidationError) as err: + serializer.validate_phone_number(bisagouser.phone_number) + + self.assertEqual(err.expected, serializers.ValidationError) + self.assertEqual('phone number already exists.', err.exception.args[0]) + + def test_validate_phone_number_valid(self): + user = User.objects.create(username='dummy username') + BisaGoUser.objects.create(user=user, phone_number='000011112222') + + serializer = RegisterUserSerializer() + + phone_number = serializer.validate_phone_number('000011112223') + + self.assertEqual(phone_number, '000011112223') + + @patch('registrasi.serializers.send_activation_email') + def test_create_and_send_activation_email_is_called(self, mock_activation_email): + data = { + 'name': 'Dummy the boi', + 'email': 'dummy@test.com', + 'password': 'password', + 'phone_number': '000011112222', + 'tanggal_lahir': '2000-01-01', + 'jenis_kelamin': 'laki-laki', + 'foto': self.image, + 'organisasi_komunitas': 'komunitas lain', + } + + request = MockRequest() + serializer = RegisterUserSerializer(data=data, context={'request':request}) + + serializer.is_valid(raise_exception=True) + serializer.save() + + user = User.objects.get(username='dummy@test.com') + mock_activation_email.assert_called_once_with(user, request) + + @patch('registrasi.serializers.print') + @patch('registrasi.serializers.send_activation_email', side_effect=ConnectionError()) + def test_create_and_send_activation_email_connection_error(self, + mock_activation_email, mock_print_connection_error): + data = { + 'name': 'Dummy the boi', + 'email': 'dummy@test.com', + 'password': 'password', + 'phone_number': '000011112222', + 'tanggal_lahir': '2000-01-01', + 'jenis_kelamin': 'laki-laki', + 'foto': self.image, + 'organisasi_komunitas': 'komunitas lain', + } + + request = MockRequest() + serializer = RegisterUserSerializer(data=data, context={'request':request}) + + serializer.is_valid(raise_exception=True) + + serializer.save() + + mock_print_connection_error.assert_called_once_with( + 'Failed to send activation email to %s, connection error.' %\ + (data['email'])) -- GitLab From 27e1991fd5d21402347835296d7a8ff3e88e7604 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Wed, 26 May 2021 23:01:15 +0700 Subject: [PATCH 15/48] [GREEN] implemented seen and organisasi_komunitas field on BisaGoUser - better email message with user.last_name and build absolute uri from request - implemented serializer for update and detail BisaGoUser - implemented serializer for register user - fix linters --- informasi_fasilitas/admin.py | 2 +- informasi_fasilitas/serializers.py | 8 +- informasi_fasilitas/urls.py | 4 +- informasi_fasilitas/views.py | 1 + layanan_khusus/tests.py | 5 +- new_rest_api/permissions.py | 14 ++ new_rest_api/tokens.py | 6 +- new_rest_api/urls.py | 18 +- new_rest_api/utils.py | 19 ++ new_rest_api/views.py | 191 ++++-------------- .../migrations/0008_auto_20210525_1756.py | 18 ++ .../0009_bisagouser_organisasi_komunitas.py | 18 ++ registrasi/models.py | 15 +- registrasi/serializers.py | 129 ++++++++++-- templates/acc_active_email.html | 4 +- 15 files changed, 261 insertions(+), 191 deletions(-) create mode 100644 new_rest_api/permissions.py create mode 100644 new_rest_api/utils.py create mode 100644 registrasi/migrations/0008_auto_20210525_1756.py create mode 100644 registrasi/migrations/0009_bisagouser_organisasi_komunitas.py diff --git a/informasi_fasilitas/admin.py b/informasi_fasilitas/admin.py index 608afb0..da05be4 100644 --- a/informasi_fasilitas/admin.py +++ b/informasi_fasilitas/admin.py @@ -6,7 +6,7 @@ from .models import Lokasi, Fasilitas, Komentar, Kegiatan, KomentarKegiatan, Fot class KomentarAdmin(admin.ModelAdmin): - list_display = ('__str__', 'deskripsi', 'user', 'notify_to') + list_display = ('__str__', 'user', 'notify_to') actions = ('send_notification',) def send_notification(self, request, queryset): diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 8b034ae..20fc38a 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -32,10 +32,11 @@ class FotoKegiatanSerializer(serializers.ModelSerializer): class KegiatanSerializer(serializers.ModelSerializer): 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) class Meta: model = Kegiatan - fields = ('id', 'place_id', 'creator', 'lokasi', 'user', + fields = ('id', 'place_id', 'creator', 'lokasi', 'user', 'creator_email', 'nama_kegiatan', 'penyelenggara', 'deskripsi', "links","narahubung", 'time_start', 'time_end') extra_kwargs = { @@ -50,9 +51,12 @@ class KegiatanSerializer(serializers.ModelSerializer): class KomentarKegiatanSerializer(serializers.ModelSerializer): kegiatan = serializers.IntegerField(source='kegiatan.id', read_only=True) creator = serializers.CharField(source='user.last_name', read_only=True) + creator_email = serializers.CharField(source='user.email', read_only=True) + class Meta: model = KomentarKegiatan - fields = ('id', 'creator', 'kegiatan', 'deskripsi', 'created') + fields = ('id', 'creator', 'kegiatan', 'deskripsi', 'created', + 'creator_email') extra_kwargs = { 'deskripsi': {'required': True}, 'created': {'required' : True} diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 6442517..04a9c4f 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -38,7 +38,7 @@ urlpatterns = [ path('lokasi/kegiatan-terdekat', views_kegiatan.nearest_kegiatan, name='nearest-kegiatan'), - + path('lokasi/search-kegiatan/', views_kegiatan.search_kegiatan, name='search-kegiatan'), @@ -59,7 +59,7 @@ urlpatterns = [ path('lokasi/add-komentar-kegiatan//', views_komentar_kegiatan.add_komentar_kegiatan, name='add-komentar-kegiatan'), - + path('lokasi/delete-komentar-kegiatan///', views_komentar_kegiatan.delete_komentar_kegiatan, name='delete-komentar-kegiatan'), diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index a52ef9c..6c49955 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -270,6 +270,7 @@ def list_komentar(request, place_id, id): komentar_details["id"] = komentar.id komentar_details["deskripsi"] = komentar.deskripsi komentar_details["creator"] = komentar.user.last_name + komentar_details["creator_email"] = komentar.user.email komentar_details["date_time"] = komentar.date_time.strftime( TIME_FORMAT) return JsonResponse(return_json, status=HTTPStatus.OK) diff --git a/layanan_khusus/tests.py b/layanan_khusus/tests.py index a321413..278f86d 100644 --- a/layanan_khusus/tests.py +++ b/layanan_khusus/tests.py @@ -1,13 +1,10 @@ import json from http import HTTPStatus -from django.test import TestCase, Client, override_settings -from django.conf import settings +from django.test import TestCase, Client from django.db.utils import IntegrityError from django.contrib.auth.models import User from django.urls import path, include, reverse -import django - from pplbackend.utils import get_client_login_with_user from .models import Sekolah, Penyandang, Komunitas from .serializers import SekolahSerializer, KomunitasSerializer diff --git a/new_rest_api/permissions.py b/new_rest_api/permissions.py new file mode 100644 index 0000000..8666c40 --- /dev/null +++ b/new_rest_api/permissions.py @@ -0,0 +1,14 @@ +from rest_framework import permissions + +class UserViewPermission(permissions.BasePermission): + def has_permission(self, request, view): + if view.action in ['register', 'retrieve', 'activate']: + return True + if view.action == 'update': + return request.user.is_authenticated + return False + + def has_object_permission(self, request, view, obj): + if view.action == 'update': + return request.user.id == obj.user.id + return True diff --git a/new_rest_api/tokens.py b/new_rest_api/tokens.py index 06aee6a..e8b35d2 100644 --- a/new_rest_api/tokens.py +++ b/new_rest_api/tokens.py @@ -1,9 +1,13 @@ from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.utils import six + + class TokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): return ( six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active) ) -account_activation_token = TokenGenerator() \ No newline at end of file + + +account_activation_token = TokenGenerator() diff --git a/new_rest_api/urls.py b/new_rest_api/urls.py index f6456d2..e9b5875 100644 --- a/new_rest_api/urls.py +++ b/new_rest_api/urls.py @@ -1,14 +1,12 @@ -from django.urls import path -import new_rest_api.views +from django.urls import path, include +from rest_framework.routers import SimpleRouter + +from . import views + +user_router = SimpleRouter() +user_router.register('', views.UserViewSet, basename='user') urlpatterns = [ - path('user-list/', new_rest_api.views.user_list, name='user-list'), - path('user-detail/', new_rest_api.views.user_details, name='user-details'), - path('user-detail/?email=', - new_rest_api.views.user_details, name='user-details-get'), - path('update-user/', new_rest_api.views.update_user, name='update-user'), - path('register/', new_rest_api.views.register_user, name='create-user'), - path(r'^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - new_rest_api.views.activate, name='activate'), + path('user/', include(user_router.urls)), ] diff --git a/new_rest_api/utils.py b/new_rest_api/utils.py new file mode 100644 index 0000000..819ee5d --- /dev/null +++ b/new_rest_api/utils.py @@ -0,0 +1,19 @@ +from django.template.loader import render_to_string +from django.utils.http import urlsafe_base64_encode +from django.utils.encoding import force_bytes +from django.core.mail import EmailMessage +from django.urls import reverse + +from .tokens import account_activation_token + +def send_activation_email(user, request, subject='Activate your account'): + uidb64 = urlsafe_base64_encode(force_bytes(user.pk)) + token = account_activation_token.make_token(user) + absolute_uri = request.build_absolute_uri(reverse('user-activate', + kwargs={'uidb64':uidb64, 'token':token})) + message = render_to_string('acc_active_email.html',{ + 'user': user, + 'absolute_uri': absolute_uri, + }) + mail = EmailMessage(subject, message, to=[user.email]) + mail.send() diff --git a/new_rest_api/views.py b/new_rest_api/views.py index 628bb68..4e92703 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -1,165 +1,56 @@ from http import HTTPStatus as status from django.contrib.auth.models import User -from django.http import JsonResponse, QueryDict -from django.views.decorators.csrf import csrf_exempt +from django.http import JsonResponse +from django.utils.encoding import force_text +from django.utils.http import urlsafe_base64_decode -from rest_framework.decorators import api_view, permission_classes, authentication_classes -from rest_framework.utils.serializer_helpers import ReturnDict - -from django.db.utils import IntegrityError -from django.utils.encoding import force_bytes, force_text -from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode -from django.template.loader import render_to_string -from django.core.mail import EmailMessage -from django.core.exceptions import ObjectDoesNotExist +from rest_framework import viewsets +from rest_framework.authentication import TokenAuthentication +from rest_framework.decorators import action +from rest_framework.response import Response from registrasi.models import BisaGoUser -from registrasi.serializers import BisaGoUserSerializers +from registrasi.serializers import BisaGoUserSerializers, RegisterUserSerializer from .tokens import account_activation_token +from .permissions import UserViewPermission -from django.core.files.storage import default_storage -from django.core.files.base import ContentFile -from django.conf import settings - - -def request_error_message(request_kind): - return "get {} request instead".format(request_kind) +ACTIVATION_URL_REGEX =\ + r'activate/(?P[0-9A-Za-z_-]+)/(?P[0-9A-Za-z]+-[0-9A-Za-z]{32})' -def missing_key_message(key): - return "bad request. {} key needed".format(key) +class UserViewSet(viewsets.ModelViewSet): + queryset = BisaGoUser.objects.all() + serializer_class = BisaGoUserSerializers + lookup_field = 'user__username' + lookup_value_regex = '[^/]+' + authentication_classes = (TokenAuthentication,) + permission_classes = (UserViewPermission,) -@csrf_exempt -@api_view(['GET']) -@authentication_classes([]) -@permission_classes([]) -def user_list(request): - if request.method == 'GET': - json_return = [] - for user in BisaGoUser.objects.all(): - json_return.append({"username":user.user.email, - "name": user.user.last_name, - "email": user.user.email, - "phone_number": user.phone_number}) - return JsonResponse(json_return, safe=False, status=status.OK) - else: - return JsonResponse({'response' : request_error_message("get")}, - status=status.METHOD_NOT_ALLOWED) + @action(detail=False, methods=['POST'], serializer_class=RegisterUserSerializer) + def register(self, request): + data = request.data + serializer = self.get_serializer(data=data, context={'request':request}) + serializer.is_valid(raise_exception=True) + instance = serializer.save() + return Response({ + 'response': 'User registered, check activation link on your email', + 'email':instance.email, 'name':instance.last_name}, + status=status.CREATED) -@api_view(['GET']) -@authentication_classes([]) -@permission_classes([]) -def user_details(request, email): - try: - if request.method == 'GET': - user = User.objects.get(username=email) - bisa_go_user = BisaGoUser.objects.get(user=user) - serializer = BisaGoUserSerializers(bisa_go_user) - json_return = {"username":user.email, - "name": user.last_name, - "email": user.email, - "phone_number": bisa_go_user.phone_number} - json_return.update(serializer.data) - if (json_return["seen"] == False) : - json_return.pop("phone_number") - json_return.pop("email") - json_return.pop("alamat") - return JsonResponse(json_return, safe=False, status=status.OK) - else: - return JsonResponse({'response' : request_error_message("get")}, - status=status.METHOD_NOT_ALLOWED) - except ObjectDoesNotExist: - return JsonResponse({'response': 'User not found'}, status=status.NOT_FOUND) - -@api_view(['POST']) -@authentication_classes([]) -@permission_classes([]) -def register_user(request): - try: - if request.method == 'POST': - name = request.POST['name'] - email = request.POST['email'] - password = request.POST['password'] - user = User.objects.create_user(username=email, email=email, - password=password, last_name=name) - data = dict(list(request.POST.dict().items())[3:]) - data['user'] = user.pk - data['seen'] = True - user.is_active = False - user.save() - mail_subject = "Activate your account" - message = render_to_string('acc_active_email.html', { - 'user' : user, - 'domain' : request.get_host, - 'uid' : urlsafe_base64_encode(force_bytes(user.pk)), - 'token' : account_activation_token.make_token(user), - }) - mail = EmailMessage(mail_subject, message, to=[email]) - mail.send() - data_query_dict = QueryDict('', mutable=True) - data_query_dict.update(data) - serializer = BisaGoUserSerializers(data=data_query_dict) - if serializer.is_valid(): - serializer.save() - return JsonResponse({'response' : 'User created', 'email':email, 'name':name}, - status=status.CREATED) - else: - return JsonResponse(serializer.errors, status=status.BAD_REQUEST) - except KeyError as error: - return JsonResponse({'response' : missing_key_message(str(error))}, - status=status.INTERNAL_SERVER_ERROR) - except IntegrityError as error: - return JsonResponse({'response' : 'User is already exist'}, - status=status.INTERNAL_SERVER_ERROR) + @action(detail=False, methods=['GET'], url_path=ACTIVATION_URL_REGEX) + def activate(self, request, uidb64, token): + return activate(uidb64, token) -@api_view(['GET']) -@authentication_classes([]) -@permission_classes([]) -def activate(request, uidb64, token): - if request.method == 'GET': - try: - uid = force_text(urlsafe_base64_decode(uidb64)) - user = User.objects.get(pk=uid) - except(TypeError, ValueError, OverflowError, User.DoesNotExist): - user = None - if user is not None and account_activation_token.check_token(user, token): - user.is_active = True - user.save() - return JsonResponse({'response' : 'User activated'}, status=status.CREATED) - else: - return JsonResponse({'response' : request_error_message('get')}, - status=status.BAD_REQUEST) - else: - return JsonResponse({'response' : request_error_message("get")}, status=status.BAD_REQUEST) -@api_view(['POST']) -@authentication_classes([]) -@permission_classes([]) -def update_user(request): +def activate(uidb64, token): try: - if request.method == 'POST': - data = dict(list(request.POST.dict().items())) - if request.FILES : - type = request.FILES['foto'].content_type.split("/")[0] - if type == "image" : - data['foto'] = request.FILES['foto'] - else : - return JsonResponse({'response': 'File is not image type'}, safe=False, - status=status.INTERNAL_SERVER_ERROR) - email = request.POST['email'] - user = User.objects.get(username=email) - data["user"] = user.pk - bisa_go_user = BisaGoUser.objects.get(user=user) - data_query_dict = QueryDict('', mutable=True) - data_query_dict.update(data) - serializer = BisaGoUserSerializers(bisa_go_user, data=data_query_dict) - if serializer.is_valid(): - user.save() - serializer.save() - else: - return JsonResponse({'response': 'Internal server error'}, safe=False, - status=status.INTERNAL_SERVER_ERROR) - return JsonResponse({'response': 'User updated'}, safe=False, status=status.OK) - except ObjectDoesNotExist: - return JsonResponse({'response': 'User not found'}, status=status.NOT_FOUND) + user_pk = force_text(urlsafe_base64_decode(uidb64)) + user = User.objects.get(pk=user_pk) + except (TypeError, ValueError, OverflowError, User.DoesNotExist): + return JsonResponse({'response': 'error'}, status=status.BAD_REQUEST) + if account_activation_token.check_token(user, token): + user.is_active = True + user.save() + return JsonResponse({'response': 'User activated'}, status=status.OK) + return JsonResponse({'response': 'error'}, status=status.BAD_REQUEST) diff --git a/registrasi/migrations/0008_auto_20210525_1756.py b/registrasi/migrations/0008_auto_20210525_1756.py new file mode 100644 index 0000000..3422383 --- /dev/null +++ b/registrasi/migrations/0008_auto_20210525_1756.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.7 on 2021-05-25 17:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0007_merge_20210517_0447'), + ] + + operations = [ + migrations.AlterField( + model_name='bisagouser', + name='foto', + field=models.ImageField(blank=True, default=None, null=True, upload_to='', verbose_name='Foto'), + ), + ] diff --git a/registrasi/migrations/0009_bisagouser_organisasi_komunitas.py b/registrasi/migrations/0009_bisagouser_organisasi_komunitas.py new file mode 100644 index 0000000..c94e3ac --- /dev/null +++ b/registrasi/migrations/0009_bisagouser_organisasi_komunitas.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.7 on 2021-05-26 15:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0008_auto_20210525_1756'), + ] + + operations = [ + migrations.AddField( + model_name='bisagouser', + name='organisasi_komunitas', + field=models.CharField(default='-', max_length=64, null=True, verbose_name='Organisasi / Komunitas'), + ), + ] diff --git a/registrasi/models.py b/registrasi/models.py index 3363a3f..d96d046 100644 --- a/registrasi/models.py +++ b/registrasi/models.py @@ -1,20 +1,23 @@ +from datetime import date + from django.db import models -from django.core.mail import send_mail from django.contrib.auth.models import User -from datetime import date class BisaGoUser(models.Model): - class Meta: - db_table = "BisaGoUser" user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="phone_number") phone_number = models.CharField('Nomor Telepon', max_length=15, unique=True) tanggal_lahir = models.DateField('Tanggal Lahir', max_length=15, null=False, default=date.today) jenis_kelamin = models.CharField('Jenis Kelamin', max_length=20, default='-') disabilitas = models.CharField('Disabilitas', max_length=64, null=True, default='Belum memilih') + organisasi_komunitas = models.CharField('Organisasi / Komunitas', max_length=64, null=True, default='-') pekerjaan = models.CharField('Pekerjaan', max_length=64, default='-') alamat = models.CharField('Alamat', max_length=255, default='-') - foto = models.ImageField('Foto', blank=True, default='-', null=True) + foto = models.ImageField('Foto', blank=True, default=None, null=True) seen = models.BooleanField('Seen', blank=True, default=True) + + class Meta: + db_table = "BisaGoUser" + def __str__(self): - return self.user.username \ No newline at end of file + return self.user.username diff --git a/registrasi/serializers.py b/registrasi/serializers.py index addcd1d..8b4c2a5 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -1,21 +1,124 @@ from rest_framework import serializers -from django.db import models + +from django.contrib.auth.models import User +from django.utils.regex_helper import _lazy_re_compile + +from new_rest_api.utils import send_activation_email from .models import BisaGoUser + class BisaGoUserSerializers(serializers.ModelSerializer): + username = serializers.CharField(source='user.username', read_only=True) + name = serializers.CharField(source='user.last_name') + email = serializers.CharField(source='user.email', read_only=True) + class Meta: model = BisaGoUser fields = ( - 'user', - 'phone_number', - 'tanggal_lahir', - 'jenis_kelamin', - 'disabilitas', - 'pekerjaan', - 'alamat', - 'foto', - 'seen', + 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'name', 'email', + 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', + 'organisasi_komunitas', ) - extra_kwargs = { - 'foto': {'required': False}, - } \ No newline at end of file + hidden_fields = ('phone_number', 'email', 'alamat') + hidden_replacement_char = '-' + update_fields_mapper = { + 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', + 'jenis_kelamin': 'jenis_kelamin', 'tanggal_lahir': 'tanggal_lahir', + 'disabilitas': 'disabilitas', 'pekerjaan': 'pekerjaan', + 'foto': 'foto', 'organisasi_komunitas': 'organisasi_komunitas' + } + update_user_fields_mapper = { + 'last_name': 'last_name', + } + + def can_see_hidden_fields(self, instance): + request = self.context['request'] + return request.user.id == instance.user.id or instance.seen + + def to_representation(self, instance): + representation = super().to_representation(instance) + if not self.can_see_hidden_fields(instance): + for hidden_field in self.Meta.hidden_fields: + representation[hidden_field] = self.Meta.hidden_replacement_char + return representation + + def update(self, instance, validated_data): + for key, value in self.Meta.update_fields_mapper.items(): + setattr(instance, key, validated_data.get(value)) + instance.save() + + user = instance.user + for key, value in self.Meta.update_user_fields_mapper.items(): + setattr(user, key, validated_data['user'].get(value)) + user.save() + return instance + + +class RegisterUserSerializer(serializers.ModelSerializer): + phone_regex = _lazy_re_compile('^(?:[+0]9)?[0-9]{11,12}$') + + name = serializers.CharField() + email = serializers.EmailField() + phone_number = serializers.CharField() + jenis_kelamin = serializers.CharField() + tanggal_lahir = serializers.DateField(format='%Y-%m-%d') + password = serializers.CharField(style={'input_type': 'password'}, + write_only=True) + + disabilitas = serializers.CharField(required=False) + organisasi_komunitas = serializers.CharField(required=False) + pekerjaan = serializers.CharField(required=False) + alamat = serializers.CharField(required=False) + foto = serializers.ImageField(required=False) + + class Meta: + model = User + fields = ( + 'name', 'email', 'password', 'tanggal_lahir', 'jenis_kelamin', + 'disabilitas', 'pekerjaan', 'alamat', 'phone_number', 'foto', + 'organisasi_komunitas', + ) + user_fields_mapper = { + 'username': 'email', 'email': 'email', 'password': 'password', + 'last_name': 'name', + } + bisago_fields_mapper = { + 'phone_number': 'phone_number', 'tanggal_lahir': 'tanggal_lahir', + 'jenis_kelamin': 'jenis_kelamin', 'disabilitas': 'disabilitas', + 'pekerjaan': 'pekerjaan', 'alamat': 'alamat', 'foto': 'foto', + 'user': 'user', 'organisasi_komunitas': 'organisasi_komunitas', + } + + def validate_email(self, value): + if User.objects.filter(username=value).exists(): + raise serializers.ValidationError('email already exists.') + return value + + def validate_phone_number(self, value): + matches = self.phone_regex.search(value) + if not matches: + raise serializers.ValidationError('invalid phone number.') + if BisaGoUser.objects.filter(phone_number=value).exists(): + raise serializers.ValidationError('phone number already exists.') + return value + + def create(self, validated_data): + user = User.objects.create_user( + **{key:validated_data.get(value) for key, value in\ + self.Meta.user_fields_mapper.items()\ + if validated_data.get(value)}, + is_active=False) + + validated_data['user'] = user + BisaGoUser.objects.create( + **{key:validated_data.get(value) for key, value in\ + self.Meta.bisago_fields_mapper.items()\ + if validated_data.get(value)}) + + try: + request = self.context['request'] + send_activation_email(user, request) + except ConnectionError: + print('Failed to send activation email to %s, connection error.' %\ + (user.username)) + return user diff --git a/templates/acc_active_email.html b/templates/acc_active_email.html index f900ca0..6319940 100644 --- a/templates/acc_active_email.html +++ b/templates/acc_active_email.html @@ -1,8 +1,8 @@ {% autoescape off %} -Hai {{ user.username }}, +Hai {{ user.last_name }}, Selamat datang di aplikasi bisaGO. Sebelum anda bisa menggunakan akun anda, silahkan melakukan aktivasi dengan meng-klik link di bawah ini. -http://{{ domain }}{% url 'activate' uidb64=uid token=token %} +{{ absolute_uri }} Terima kasih dan selamat menggunakan bisaGO. -- GitLab From aa0a4dadbd2a6ee1b5d9aacd6b3ee895541f165b Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Wed, 26 May 2021 23:57:12 +0700 Subject: [PATCH 16/48] [RED] set tanggal_lahir as hidden field --- registrasi/test_serializers.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index 1c56031..d1dcf31 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -31,7 +31,8 @@ class BaseTestSerializer(TestCase): } mock_bisagouser = { 'phone_number': '081208120812', - 'alamat': 'Dummy St' + 'alamat': 'Dummy St', + 'tanggal_lahir': '2000-01-01', } def setUp(self): @@ -51,7 +52,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): def test_meta_hidden_fields(self): self.assertEqual(BisaGoUserSerializers.Meta.hidden_fields, - ('phone_number', 'email', 'alamat')) + ('phone_number', 'email', 'alamat', 'tanggal_lahir')) def test_meta_hidden_replacement_char(self): self.assertEqual(BisaGoUserSerializers.Meta.hidden_replacement_char, '-') @@ -80,7 +81,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): context={'request':request}) expected = { - 'phone_number': '081208120812', 'tanggal_lahir': '2021-05-26', + 'phone_number': '081208120812', 'tanggal_lahir': '2000-01-01', 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': True, 'username': 'dummy username', 'name': 'dummy lastname', @@ -99,7 +100,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): context={'request':request}) expected = { - 'phone_number': '081208120812', 'tanggal_lahir': '2021-05-26', + 'phone_number': '081208120812', 'tanggal_lahir': '2000-01-01', 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', 'pekerjaan': '-', 'alamat': 'Dummy St', 'foto': None, 'seen': True, 'username': 'dummy username', 'name': 'dummy lastname', @@ -121,7 +122,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): replaced = BisaGoUserSerializers.Meta.hidden_replacement_char expected = { - 'phone_number': replaced, 'tanggal_lahir': '2021-05-26', + 'phone_number': replaced, 'tanggal_lahir': replaced, 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': False, 'username': 'dummy username', 'name': 'dummy lastname', -- GitLab From 76c71377f23d5a01d9fdec180eb527be8603b437 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Wed, 26 May 2021 23:58:10 +0700 Subject: [PATCH 17/48] [GREEN] implemented tanggal lahir is hidden field --- registrasi/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registrasi/serializers.py b/registrasi/serializers.py index 8b4c2a5..8690a55 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -19,7 +19,7 @@ class BisaGoUserSerializers(serializers.ModelSerializer): 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', 'organisasi_komunitas', ) - hidden_fields = ('phone_number', 'email', 'alamat') + hidden_fields = ('phone_number', 'email', 'alamat', 'tanggal_lahir') hidden_replacement_char = '-' update_fields_mapper = { 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', -- GitLab From d7b31cf110c50900b825b589672df31344af5d32 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 10:40:31 +0700 Subject: [PATCH 18/48] [RED] tests added field hidden_fields to response on detail profile - also change serializers name from BisaGoUserSerializers to BisaGoUserSerializer --- registrasi/test_serializers.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index d1dcf31..f1c3008 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -6,7 +6,7 @@ from django.test import TestCase from rest_framework import serializers -from .serializers import BisaGoUserSerializers, RegisterUserSerializer +from .serializers import BisaGoUserSerializer, RegisterUserSerializer from .models import BisaGoUser @@ -38,27 +38,28 @@ class BaseTestSerializer(TestCase): def setUp(self): self.image = SimpleUploadedFile("test1.jpg", content=open("test_file/test1.jpg", 'rb').read(), content_type='image/jpeg') + self.hidden_fields = BisaGoUserSerializer.Meta.hidden_fields class TestBisaGoUserSerializer(BaseTestSerializer): def test_meta_model(self): - self.assertEqual(BisaGoUserSerializers.Meta.model, BisaGoUser) + self.assertEqual(BisaGoUserSerializer.Meta.model, BisaGoUser) def test_meta_fields(self): - self.assertSequenceEqual(BisaGoUserSerializers.Meta.fields, ( + self.assertSequenceEqual(BisaGoUserSerializer.Meta.fields, ( 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'name', 'email', 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', - 'organisasi_komunitas', + 'organisasi_komunitas', 'hidden_fields', )) def test_meta_hidden_fields(self): - self.assertEqual(BisaGoUserSerializers.Meta.hidden_fields, + self.assertEqual(BisaGoUserSerializer.Meta.hidden_fields, ('phone_number', 'email', 'alamat', 'tanggal_lahir')) def test_meta_hidden_replacement_char(self): - self.assertEqual(BisaGoUserSerializers.Meta.hidden_replacement_char, '-') + self.assertEqual(BisaGoUserSerializer.Meta.hidden_replacement_char, '-') def test_meta_update_fields_mapper(self): - self.assertDictEqual(BisaGoUserSerializers.Meta.update_fields_mapper, { + self.assertDictEqual(BisaGoUserSerializer.Meta.update_fields_mapper, { 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', 'jenis_kelamin': 'jenis_kelamin', 'tanggal_lahir': 'tanggal_lahir', 'disabilitas': 'disabilitas', 'pekerjaan': 'pekerjaan', @@ -66,7 +67,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): }) def test_meta_update_user_fields_mapper(self): - self.assertEqual(BisaGoUserSerializers.Meta.update_user_fields_mapper, { + self.assertEqual(BisaGoUserSerializer.Meta.update_user_fields_mapper, { 'last_name': 'last_name', }) @@ -77,7 +78,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): user_id = 99 assert user.id != user_id request = MockRequest(user_id=user_id) - bisagouser = BisaGoUserSerializers(bisagouser, + bisagouser = BisaGoUserSerializer(bisagouser, context={'request':request}) expected = { @@ -86,7 +87,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': True, 'username': 'dummy username', 'name': 'dummy lastname', 'email': 'test@email.com', 'alamat': 'Dummy St', - 'organisasi_komunitas': '-' + 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields } self.assertEqual(bisagouser.data, expected) @@ -96,7 +97,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): bisagouser = BisaGoUser.objects.create(user=user, **self.mock_bisagouser) request = MockRequest(user_id=user.id) - bisagouser = BisaGoUserSerializers(bisagouser, + bisagouser = BisaGoUserSerializer(bisagouser, context={'request':request}) expected = { @@ -105,6 +106,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'pekerjaan': '-', 'alamat': 'Dummy St', 'foto': None, 'seen': True, 'username': 'dummy username', 'name': 'dummy lastname', 'email': 'test@email.com', 'organisasi_komunitas': '-', + 'hidden_fields': self.hidden_fields, } self.assertEqual(bisagouser.data, expected) @@ -117,17 +119,17 @@ class TestBisaGoUserSerializer(BaseTestSerializer): user_id = 99 assert user_id != user.id request = MockRequest(user_id=user_id) - bisagouser = BisaGoUserSerializers(bisagouser, + bisagouser = BisaGoUserSerializer(bisagouser, context={'request':request}) - replaced = BisaGoUserSerializers.Meta.hidden_replacement_char + replaced = BisaGoUserSerializer.Meta.hidden_replacement_char expected = { 'phone_number': replaced, 'tanggal_lahir': replaced, 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': False, 'username': 'dummy username', 'name': 'dummy lastname', 'email': replaced, 'alamat': replaced, - 'organisasi_komunitas': '-' + 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields, } self.assertEqual(bisagouser.data, expected) @@ -146,7 +148,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): } request = MockRequest(user_id=user.id) - serializer = BisaGoUserSerializers(bisagouser, data=update, + serializer = BisaGoUserSerializer(bisagouser, data=update, context={'request':request}) serializer.is_valid(raise_exception=True) serializer.save() @@ -156,7 +158,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'jenis_kelamin': 'laki-laki', 'disabilitas': 'tanpa batas', 'pekerjaan': 'makan', 'alamat': 'dif dummy st', 'seen': False, 'username': 'dummy username', 'name': 'another dummy name', - 'email': 'test@email.com', + 'email': 'test@email.com', 'hidden_fields': self.hidden_fields, 'organisasi_komunitas': 'komunitas tanpa batas' } -- GitLab From 7e470323a81e18fe9c1e013a57b5b2b3eadb8a8e Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 10:43:14 +0700 Subject: [PATCH 19/48] [GREEN] added hidden_fields to response on detail profile --- new_rest_api/views.py | 4 ++-- registrasi/serializers.py | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/new_rest_api/views.py b/new_rest_api/views.py index 4e92703..845f939 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -11,7 +11,7 @@ from rest_framework.decorators import action from rest_framework.response import Response from registrasi.models import BisaGoUser -from registrasi.serializers import BisaGoUserSerializers, RegisterUserSerializer +from registrasi.serializers import BisaGoUserSerializer, RegisterUserSerializer from .tokens import account_activation_token from .permissions import UserViewPermission @@ -21,7 +21,7 @@ ACTIVATION_URL_REGEX =\ class UserViewSet(viewsets.ModelViewSet): queryset = BisaGoUser.objects.all() - serializer_class = BisaGoUserSerializers + serializer_class = BisaGoUserSerializer lookup_field = 'user__username' lookup_value_regex = '[^/]+' authentication_classes = (TokenAuthentication,) diff --git a/registrasi/serializers.py b/registrasi/serializers.py index 8690a55..cbd4f07 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -7,17 +7,19 @@ from new_rest_api.utils import send_activation_email from .models import BisaGoUser -class BisaGoUserSerializers(serializers.ModelSerializer): +class BisaGoUserSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) name = serializers.CharField(source='user.last_name') email = serializers.CharField(source='user.email', read_only=True) + hidden_fields = serializers.SerializerMethodField() + class Meta: model = BisaGoUser fields = ( 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'name', 'email', 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', - 'organisasi_komunitas', + 'organisasi_komunitas', 'hidden_fields', ) hidden_fields = ('phone_number', 'email', 'alamat', 'tanggal_lahir') hidden_replacement_char = '-' @@ -31,6 +33,9 @@ class BisaGoUserSerializers(serializers.ModelSerializer): 'last_name': 'last_name', } + def get_hidden_fields(self, *args): + return self.Meta.hidden_fields + def can_see_hidden_fields(self, instance): request = self.context['request'] return request.user.id == instance.user.id or instance.seen -- GitLab From cc2c051693a02d2d67b37817f80f182621209049 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 12:12:33 +0700 Subject: [PATCH 20/48] [RED] tests for new field 'hidden_fields' that listing all hidden fields from serializer --- registrasi/test_serializers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index f1c3008..9161400 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -38,7 +38,7 @@ class BaseTestSerializer(TestCase): def setUp(self): self.image = SimpleUploadedFile("test1.jpg", content=open("test_file/test1.jpg", 'rb').read(), content_type='image/jpeg') - self.hidden_fields = BisaGoUserSerializer.Meta.hidden_fields + self.hidden_fields = ['nomor telepon','email','alamat','tanggal lahir'] class TestBisaGoUserSerializer(BaseTestSerializer): def test_meta_model(self): @@ -55,6 +55,10 @@ class TestBisaGoUserSerializer(BaseTestSerializer): self.assertEqual(BisaGoUserSerializer.Meta.hidden_fields, ('phone_number', 'email', 'alamat', 'tanggal_lahir')) + def test_meta_hidden_fields_verbose(self): + self.assertEqual(BisaGoUserSerializer.Meta.hidden_fields_verbose, + {'phone_number': 'nomor telepon', 'tanggal_lahir': 'tanggal lahir'}) + def test_meta_hidden_replacement_char(self): self.assertEqual(BisaGoUserSerializer.Meta.hidden_replacement_char, '-') -- GitLab From 26ce8655ae16b679b7c79372f5b73a1bd21e7e55 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 12:13:49 +0700 Subject: [PATCH 21/48] [GREEN] implemented new field 'hidden_fields' that listing all hidden fields from serializer --- registrasi/serializers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/registrasi/serializers.py b/registrasi/serializers.py index cbd4f07..729a754 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -22,6 +22,10 @@ class BisaGoUserSerializer(serializers.ModelSerializer): 'organisasi_komunitas', 'hidden_fields', ) hidden_fields = ('phone_number', 'email', 'alamat', 'tanggal_lahir') + hidden_fields_verbose = { + 'phone_number': 'nomor telepon', + 'tanggal_lahir': 'tanggal lahir', + } hidden_replacement_char = '-' update_fields_mapper = { 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', @@ -34,7 +38,10 @@ class BisaGoUserSerializer(serializers.ModelSerializer): } def get_hidden_fields(self, *args): - return self.Meta.hidden_fields + hidden_fields = self.Meta.hidden_fields + verbose = self.Meta.hidden_fields_verbose + return [verbose.get(field) if verbose.get(field) else field\ + for field in hidden_fields] def can_see_hidden_fields(self, instance): request = self.context['request'] -- GitLab From 552e0bfc4261b05af808d6a55793d7ab8b501da2 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 19:50:35 +0700 Subject: [PATCH 22/48] [RED] tests for refactor serializers and add template for activation email --- new_rest_api/test_views.py | 25 +++++++++++++++++----- registrasi/test_serializers.py | 38 +++++++++++++--------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/new_rest_api/test_views.py b/new_rest_api/test_views.py index 4fa9308..e2c06a1 100644 --- a/new_rest_api/test_views.py +++ b/new_rest_api/test_views.py @@ -88,7 +88,18 @@ class TestUserViews(TestCase): resp = self.client.get(activation_url) data = response_decode(resp) - expected = {'response': 'User activated'} + expected = f''' +Hai {user.last_name}, +
+
+
+User anda berhasil teraktivasi, terima kasih telah menggunakan bisaGo. +
+
+Salam, +
+bisaGo dev Team +''' user.refresh_from_db() self.assertTrue(user.is_active) @@ -116,11 +127,13 @@ class TestUserViews(TestCase): resp = self.client.get(activation_url) data = response_decode(resp) - expected = {'response': 'error'} + expected = ''' +Terjadi kesalahan atau akun sudah teraktivasi, mohon hubungi tim pengembang bisaGo. +''' user.refresh_from_db() self.assertFalse(user.is_active) - self.assertEqual(resp.status_code, status.BAD_REQUEST) + self.assertEqual(resp.status_code, status.OK) self.assertEqual(data, expected) @patch('new_rest_api.views.account_activation_token.check_token', return_value=False) @@ -145,9 +158,11 @@ class TestUserViews(TestCase): resp = self.client.get(activation_url) data = response_decode(resp) - expected = {'response': 'error'} + expected = ''' +Terjadi kesalahan atau akun sudah teraktivasi, mohon hubungi tim pengembang bisaGo. +''' user.refresh_from_db() self.assertFalse(user.is_active) - self.assertEqual(resp.status_code, status.BAD_REQUEST) + self.assertEqual(resp.status_code, status.OK) self.assertEqual(data, expected) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index 9161400..9cc4547 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -62,18 +62,20 @@ class TestBisaGoUserSerializer(BaseTestSerializer): def test_meta_hidden_replacement_char(self): self.assertEqual(BisaGoUserSerializer.Meta.hidden_replacement_char, '-') - def test_meta_update_fields_mapper(self): - self.assertDictEqual(BisaGoUserSerializer.Meta.update_fields_mapper, { - 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', - 'jenis_kelamin': 'jenis_kelamin', 'tanggal_lahir': 'tanggal_lahir', - 'disabilitas': 'disabilitas', 'pekerjaan': 'pekerjaan', - 'foto': 'foto', 'organisasi_komunitas': 'organisasi_komunitas' - }) + def test_meta_update_bisago_fields(self): + self.assertEqual(BisaGoUserSerializer.Meta.update_bisago_fields, ( + 'phone_number', 'seen', 'alamat', 'jenis_kelamin', 'tanggal_lahir', + 'disabilitas', 'pekerjaan', 'foto', 'organisasi_komunitas' + )) + + def test_meta_update_bisago_fields_mapper(self): + self.assertDictEqual(BisaGoUserSerializer.Meta.update_bisago_fields_mapper, {}) + + def test_meta_update_user_fields(self): + self.assertEqual(BisaGoUserSerializer.Meta.update_user_fields, ('last_name',)) def test_meta_update_user_fields_mapper(self): - self.assertEqual(BisaGoUserSerializer.Meta.update_user_fields_mapper, { - 'last_name': 'last_name', - }) + self.assertEqual(BisaGoUserSerializer.Meta.update_user_fields_mapper, {}) def test_can_see_hidden_fields_obj_seen_is_true_from_others(self): user = User.objects.create_user(**self.mock_user) @@ -184,25 +186,13 @@ class TestRegisterUserSerializer(BaseTestSerializer): )) def test_meta_user_fields_mapper(self): - self.assertEqual(RegisterUserSerializer.Meta.user_fields_mapper, { + self.assertEqual(RegisterUserSerializer.Meta.create_user_fields_mapper, { 'username': 'email', - 'email': 'email', - 'password': 'password', 'last_name': 'name', }) def test_meta_bisago_fields_mapper(self): - self.assertEqual(RegisterUserSerializer.Meta.bisago_fields_mapper, { - 'tanggal_lahir': 'tanggal_lahir', - 'jenis_kelamin': 'jenis_kelamin', - 'phone_number': 'phone_number', - 'disabilitas': 'disabilitas', - 'organisasi_komunitas': 'organisasi_komunitas', - 'pekerjaan': 'pekerjaan', - 'alamat': 'alamat', - 'foto': 'foto', - 'user': 'user', - }) + self.assertEqual(RegisterUserSerializer.Meta.create_bisago_fields_mapper, {}) def test_validate_email_already_exists(self): user = User.objects.create(username='dummy@test.com') -- GitLab From ad9ccf34ca8a12054a3522c860412db4e291bd59 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 19:51:25 +0700 Subject: [PATCH 23/48] [GREEN] refactor serializers, delete mapper, use tuple and added template for activation email --- new_rest_api/renderers.py | 4 + new_rest_api/views.py | 11 ++- pplbackend/utils.py | 11 ++- .../migrations/0010_auto_20210527_0722.py | 18 ++++ registrasi/models.py | 2 +- registrasi/serializers.py | 82 +++++++++++-------- templates/acc_activate_success.html | 14 ++++ 7 files changed, 99 insertions(+), 43 deletions(-) create mode 100644 new_rest_api/renderers.py create mode 100644 registrasi/migrations/0010_auto_20210527_0722.py create mode 100644 templates/acc_activate_success.html diff --git a/new_rest_api/renderers.py b/new_rest_api/renderers.py new file mode 100644 index 0000000..33065ce --- /dev/null +++ b/new_rest_api/renderers.py @@ -0,0 +1,4 @@ +from rest_framework.renderers import TemplateHTMLRenderer + +class AccountActivationHTMLRenderer(TemplateHTMLRenderer): + template_name = 'acc_activate_success.html' diff --git a/new_rest_api/views.py b/new_rest_api/views.py index 845f939..98e14bb 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -9,11 +9,13 @@ from rest_framework import viewsets from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.renderers import TemplateHTMLRenderer from registrasi.models import BisaGoUser from registrasi.serializers import BisaGoUserSerializer, RegisterUserSerializer from .tokens import account_activation_token from .permissions import UserViewPermission +from .renderers import AccountActivationHTMLRenderer ACTIVATION_URL_REGEX =\ r'activate/(?P[0-9A-Za-z_-]+)/(?P[0-9A-Za-z]+-[0-9A-Za-z]{32})' @@ -38,7 +40,8 @@ class UserViewSet(viewsets.ModelViewSet): 'email':instance.email, 'name':instance.last_name}, status=status.CREATED) - @action(detail=False, methods=['GET'], url_path=ACTIVATION_URL_REGEX) + @action(detail=False, methods=['GET'], url_path=ACTIVATION_URL_REGEX, + renderer_classes=[AccountActivationHTMLRenderer]) def activate(self, request, uidb64, token): return activate(uidb64, token) @@ -48,9 +51,9 @@ def activate(uidb64, token): user_pk = force_text(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=user_pk) except (TypeError, ValueError, OverflowError, User.DoesNotExist): - return JsonResponse({'response': 'error'}, status=status.BAD_REQUEST) + return Response({'success': 0}) if account_activation_token.check_token(user, token): user.is_active = True user.save() - return JsonResponse({'response': 'User activated'}, status=status.OK) - return JsonResponse({'response': 'error'}, status=status.BAD_REQUEST) + return Response({'success': 1, 'user': user}) + return Response({'success': 0}) diff --git a/pplbackend/utils.py b/pplbackend/utils.py index 1273caf..b915796 100644 --- a/pplbackend/utils.py +++ b/pplbackend/utils.py @@ -14,11 +14,10 @@ def get_client_login_with_user(user): return client -def response_decode( - response, -): +def response_decode(response): + content = response.content.decode('utf-8') try: - content = json.loads(response.content.decode('utf-8')) - except json.decoder.JSONDecodeError as json_error: - return None + return json.loads(content) + except json.decoder.JSONDecodeError: + pass return content diff --git a/registrasi/migrations/0010_auto_20210527_0722.py b/registrasi/migrations/0010_auto_20210527_0722.py new file mode 100644 index 0000000..8f03bc6 --- /dev/null +++ b/registrasi/migrations/0010_auto_20210527_0722.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.7 on 2021-05-27 07:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0009_bisagouser_organisasi_komunitas'), + ] + + operations = [ + migrations.AlterField( + model_name='bisagouser', + name='foto', + field=models.ImageField(blank=True, default=None, null=True, upload_to='user/', verbose_name='Foto'), + ), + ] diff --git a/registrasi/models.py b/registrasi/models.py index d96d046..29621bb 100644 --- a/registrasi/models.py +++ b/registrasi/models.py @@ -13,7 +13,7 @@ class BisaGoUser(models.Model): organisasi_komunitas = models.CharField('Organisasi / Komunitas', max_length=64, null=True, default='-') pekerjaan = models.CharField('Pekerjaan', max_length=64, default='-') alamat = models.CharField('Alamat', max_length=255, default='-') - foto = models.ImageField('Foto', blank=True, default=None, null=True) + foto = models.ImageField('Foto', upload_to='user/', blank=True, default=None, null=True) seen = models.BooleanField('Seen', blank=True, default=True) class Meta: diff --git a/registrasi/serializers.py b/registrasi/serializers.py index 729a754..6415db6 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -21,21 +21,26 @@ class BisaGoUserSerializer(serializers.ModelSerializer): 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', 'organisasi_komunitas', 'hidden_fields', ) + hidden_fields = ('phone_number', 'email', 'alamat', 'tanggal_lahir') hidden_fields_verbose = { 'phone_number': 'nomor telepon', 'tanggal_lahir': 'tanggal lahir', } hidden_replacement_char = '-' - update_fields_mapper = { - 'phone_number': 'phone_number', 'seen': 'seen', 'alamat': 'alamat', - 'jenis_kelamin': 'jenis_kelamin', 'tanggal_lahir': 'tanggal_lahir', - 'disabilitas': 'disabilitas', 'pekerjaan': 'pekerjaan', - 'foto': 'foto', 'organisasi_komunitas': 'organisasi_komunitas' - } - update_user_fields_mapper = { - 'last_name': 'last_name', - } + + update_bisago_fields = ( + 'phone_number', 'seen', 'alamat', 'jenis_kelamin', 'tanggal_lahir', + 'disabilitas', 'pekerjaan', 'foto', 'organisasi_komunitas' + ) + # example mapping field 'model_field' from 'input_field' + # {'model_field':'input_field'} + # needed when field from model is difference from input data + # see actual example on RegisterUserSerializer.Meta.create_user_fields_mapper + update_bisago_fields_mapper = {} + + update_user_fields = ('last_name',) + update_user_fields_mapper = {} def get_hidden_fields(self, *args): hidden_fields = self.Meta.hidden_fields @@ -54,14 +59,19 @@ class BisaGoUserSerializer(serializers.ModelSerializer): representation[hidden_field] = self.Meta.hidden_replacement_char return representation + def _update_instance(self, instance, validated_data, fields, mapper): + for field in fields: + mapped = mapper.get(field) or field + setattr(instance, field, validated_data.get(mapped)) + return instance + def update(self, instance, validated_data): - for key, value in self.Meta.update_fields_mapper.items(): - setattr(instance, key, validated_data.get(value)) + instance = self._update_instance(instance, validated_data, + self.Meta.update_bisago_fields, self.Meta.update_bisago_fields_mapper) instance.save() - user = instance.user - for key, value in self.Meta.update_user_fields_mapper.items(): - setattr(user, key, validated_data['user'].get(value)) + user = self._update_instance(instance.user, validated_data.get('user'), + self.Meta.update_user_fields, self.Meta.update_user_fields_mapper) user.save() return instance @@ -90,17 +100,20 @@ class RegisterUserSerializer(serializers.ModelSerializer): 'disabilitas', 'pekerjaan', 'alamat', 'phone_number', 'foto', 'organisasi_komunitas', ) - user_fields_mapper = { - 'username': 'email', 'email': 'email', 'password': 'password', - 'last_name': 'name', - } - bisago_fields_mapper = { - 'phone_number': 'phone_number', 'tanggal_lahir': 'tanggal_lahir', - 'jenis_kelamin': 'jenis_kelamin', 'disabilitas': 'disabilitas', - 'pekerjaan': 'pekerjaan', 'alamat': 'alamat', 'foto': 'foto', - 'user': 'user', 'organisasi_komunitas': 'organisasi_komunitas', + + create_user_fields = ( + 'username', 'email', 'password', 'last_name', 'is_active', + ) + create_user_fields_mapper = { + 'username': 'email', 'last_name': 'name', } + create_bisago_fields = ( + 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'disabilitas', + 'pekerjaan', 'alamat', 'foto', 'user', 'organisasi_komunitas', + ) + create_bisago_fields_mapper = {} + def validate_email(self, value): if User.objects.filter(username=value).exists(): raise serializers.ValidationError('email already exists.') @@ -114,18 +127,23 @@ class RegisterUserSerializer(serializers.ModelSerializer): raise serializers.ValidationError('phone number already exists.') return value + def _create_model(self, creator_manager, validated_data, fields, mapper): + instance_data = {} + for field in fields: + mapped = mapper.get(field) or field + value = validated_data.get(mapped) + if value is not None: + instance_data[field] = value + return creator_manager(**instance_data) + def create(self, validated_data): - user = User.objects.create_user( - **{key:validated_data.get(value) for key, value in\ - self.Meta.user_fields_mapper.items()\ - if validated_data.get(value)}, - is_active=False) + validated_data['is_active'] = False + user = self._create_model(User.objects.create_user, validated_data, + self.Meta.create_user_fields, self.Meta.create_user_fields_mapper) validated_data['user'] = user - BisaGoUser.objects.create( - **{key:validated_data.get(value) for key, value in\ - self.Meta.bisago_fields_mapper.items()\ - if validated_data.get(value)}) + self._create_model(BisaGoUser.objects.create, validated_data, + self.Meta.create_bisago_fields, self.Meta.create_bisago_fields_mapper) try: request = self.context['request'] diff --git a/templates/acc_activate_success.html b/templates/acc_activate_success.html new file mode 100644 index 0000000..8f61337 --- /dev/null +++ b/templates/acc_activate_success.html @@ -0,0 +1,14 @@ +{% if success %} +Hai {{ user.last_name }}, +
+
+
+User anda berhasil teraktivasi, terima kasih telah menggunakan bisaGo. +
+
+Salam, +
+bisaGo dev Team +{% else %} +Terjadi kesalahan atau akun sudah teraktivasi, mohon hubungi tim pengembang bisaGo. +{% endif %} \ No newline at end of file -- GitLab From 517f922c6075ea1a4e9c898ad653179e16dba386 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Thu, 27 May 2021 20:05:17 +0700 Subject: [PATCH 24/48] [CHORE] removed unused imports JsonResponse and TemplateHTMLRenderer --- new_rest_api/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/new_rest_api/views.py b/new_rest_api/views.py index 98e14bb..f275220 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -1,7 +1,6 @@ from http import HTTPStatus as status from django.contrib.auth.models import User -from django.http import JsonResponse from django.utils.encoding import force_text from django.utils.http import urlsafe_base64_decode @@ -9,7 +8,6 @@ from rest_framework import viewsets from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework.renderers import TemplateHTMLRenderer from registrasi.models import BisaGoUser from registrasi.serializers import BisaGoUserSerializer, RegisterUserSerializer -- 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 25/48] [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 26/48] [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 27/48] [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 28/48] [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 29/48] [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 94afe0cb64b84d18226a57dc8a450d36b589696c Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Fri, 28 May 2021 22:27:42 +0700 Subject: [PATCH 30/48] [CHORES] now profile photo is compressed with rate .55 --- informasi_fasilitas/test_views_kegiatan.py | 2 ++ .../migrations/0011_merge_20210528_1510.py | 14 ++++++++++++++ .../migrations/0012_auto_20210528_1519.py | 19 +++++++++++++++++++ registrasi/models.py | 4 +++- 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 registrasi/migrations/0011_merge_20210528_1510.py create mode 100644 registrasi/migrations/0012_auto_20210528_1519.py diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index 8eb9ba8..156a562 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -188,6 +188,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': kegiatan_wita.id, 'place_id': kegiatan_wita.lokasi.place_id, 'creator': kegiatan_wita.user.last_name, + 'creator_email': kegiatan_wita.user.email, 'nama_kegiatan': kegiatan_wita.nama_kegiatan, 'penyelenggara': kegiatan_wita.penyelenggara, 'deskripsi': kegiatan_wita.deskripsi, @@ -216,6 +217,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': kegiatan_wit.id, 'place_id': kegiatan_wit.lokasi.place_id, 'creator': kegiatan_wit.user.last_name, + 'creator_email': kegiatan_wit.user.email, 'nama_kegiatan': kegiatan_wit.nama_kegiatan, 'penyelenggara': kegiatan_wit.penyelenggara, 'deskripsi': kegiatan_wit.deskripsi, diff --git a/registrasi/migrations/0011_merge_20210528_1510.py b/registrasi/migrations/0011_merge_20210528_1510.py new file mode 100644 index 0000000..c5ab54b --- /dev/null +++ b/registrasi/migrations/0011_merge_20210528_1510.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.7 on 2021-05-28 15:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0010_auto_20210527_0722'), + ('registrasi', '0008_merge_20210527_2250'), + ] + + operations = [ + ] diff --git a/registrasi/migrations/0012_auto_20210528_1519.py b/registrasi/migrations/0012_auto_20210528_1519.py new file mode 100644 index 0000000..5d27ec4 --- /dev/null +++ b/registrasi/migrations/0012_auto_20210528_1519.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.7 on 2021-05-28 15:19 + +from django.db import migrations +import pplbackend.custom_model_field + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0011_merge_20210528_1510'), + ] + + operations = [ + migrations.AlterField( + model_name='bisagouser', + name='foto', + field=pplbackend.custom_model_field.CompressedImageField(blank=True, default=None, null=True, quality=55, upload_to='user/', verbose_name='Foto'), + ), + ] diff --git a/registrasi/models.py b/registrasi/models.py index 29621bb..1be6d6e 100644 --- a/registrasi/models.py +++ b/registrasi/models.py @@ -3,6 +3,8 @@ from datetime import date from django.db import models from django.contrib.auth.models import User +from pplbackend.custom_model_field import CompressedImageField + class BisaGoUser(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="phone_number") @@ -13,7 +15,7 @@ class BisaGoUser(models.Model): organisasi_komunitas = models.CharField('Organisasi / Komunitas', max_length=64, null=True, default='-') pekerjaan = models.CharField('Pekerjaan', max_length=64, default='-') alamat = models.CharField('Alamat', max_length=255, default='-') - foto = models.ImageField('Foto', upload_to='user/', blank=True, default=None, null=True) + foto = CompressedImageField('Foto', upload_to='user/', blank=True, default=None, null=True, quality=55) seen = models.BooleanField('Seen', blank=True, default=True) class Meta: -- GitLab From ed4c8bb440e9d0674496e8ae6ccc13e7c0e2d36f Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 10:40:40 +0700 Subject: [PATCH 31/48] ImageField with Compression And timezone and Kegiatan Models --- .../migrations/0017_auto_20210520_0336.py | 19 +++ .../migrations/0018_auto_20210520_0451.py | 19 +++ .../migrations/0019_auto_20210527_2251.py | 32 ++++ informasi_fasilitas/models.py | 14 +- informasi_fasilitas/serializers.py | 41 ++++- informasi_fasilitas/test_base.py | 87 ++++++++-- informasi_fasilitas/test_views_kegiatan.py | 154 ++++++++++++------ informasi_fasilitas/views_kegiatan.py | 18 +- pplbackend/custom_model_field.py | 38 +++++ pplbackend/settings.py | 2 +- .../migrations/0006_merge_20210519_1213.py | 14 ++ .../migrations/0008_merge_20210527_2250.py | 14 ++ requirements.txt | 12 +- 13 files changed, 389 insertions(+), 75 deletions(-) create mode 100644 informasi_fasilitas/migrations/0017_auto_20210520_0336.py create mode 100644 informasi_fasilitas/migrations/0018_auto_20210520_0451.py create mode 100644 informasi_fasilitas/migrations/0019_auto_20210527_2251.py create mode 100644 pplbackend/custom_model_field.py create mode 100644 registrasi/migrations/0006_merge_20210519_1213.py create mode 100644 registrasi/migrations/0008_merge_20210527_2250.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/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 fe0c90d..ae59d6a 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 @@ -45,6 +45,12 @@ JENIS_DISABILITAS = ( ('DS', 'Disabilitas Sensorik'), ) +TIMEZONE_INDONESIA = ( + ('WIB', 'WIB'), + ('WITA', 'WITA'), + ('WIT', 'WIT'), +) + 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) @@ -122,4 +130,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) 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/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 5802e0d..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, @@ -51,6 +51,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) @@ -61,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 = \ @@ -87,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() @@ -134,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, }, @@ -150,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) @@ -211,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={ @@ -226,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) @@ -246,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() @@ -282,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) @@ -313,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() @@ -341,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) 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): 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 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': [ 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 = [ + ] 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 = [ + ] diff --git a/requirements.txt b/requirements.txt index 589b95d..e50ab4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,8 +4,11 @@ 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 @@ -19,9 +22,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 +36,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 +48,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 +62,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 13a684cda4863be8bf13dc3e1a2668d7b4ca6992 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 19:56:05 +0700 Subject: [PATCH 32/48] 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 bb3f9ebc441d888a7a5538e4883f41161317fa58 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Sat, 29 May 2021 21:56:11 +0700 Subject: [PATCH 33/48] Dev rafif --- informasi_fasilitas/serializers.py | 5 ++--- informasi_fasilitas/test_base.py | 12 ++++++------ pplbackend/settings.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 9a15b98..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,8 +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/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 = { 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 357d4bcf9ed3c6a82498290b7fac3974acc6bde5 Mon Sep 17 00:00:00 2001 From: Christopher Samuel Date: Sun, 30 May 2021 18:09:15 +0700 Subject: [PATCH 34/48] [CHORES] Add default query for search kegiatan --- informasi_fasilitas/urls.py | 3 +++ informasi_fasilitas/views_kegiatan.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 05f598d..d69013c 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -39,6 +39,9 @@ urlpatterns = [ path('lokasi/kegiatan-terdekat', views_kegiatan.nearest_kegiatan, name='nearest-kegiatan'), + path('lokasi/search-kegiatan', + views_kegiatan.search_kegiatan, name='search-kegiatan'), + path('lokasi/search-kegiatan/', views_kegiatan.search_kegiatan, name='search-kegiatan'), diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 4a165b0..474f17b 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -98,7 +98,7 @@ def list_foto_kegiatan(request, place_id, kegiatan_id): @api_view(['GET']) @authentication_classes([]) @permission_classes([]) -def search_kegiatan(request, query): +def search_kegiatan(request, query=''): query_by_nama = Kegiatan.objects.filter(nama_kegiatan__icontains=query) query_by_deskripsi = Kegiatan.objects.filter(deskripsi__icontains=query) query_by_penyelenggara = Kegiatan.objects.filter(penyelenggara__icontains=query) -- GitLab From 26b9060dee78c3fcc70efb05dacaf1aded690e89 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Mon, 31 May 2021 18:42:21 +0700 Subject: [PATCH 35/48] [REFACTOR] related to informasi_fasilitas models and notification: - now KomentarFasilitas and KomentarKegiatan models is subclass from Komentar - change name of date_time field on KomentarFasilitas to created - improved send_komentar_notification to check if fcm target is exists first then send --- informasi_fasilitas/admin.py | 6 +- .../migrations/0017_auto_20210531_1003.py | 24 ++++++ informasi_fasilitas/models.py | 18 ++--- informasi_fasilitas/test_admin.py | 2 +- informasi_fasilitas/test_base.py | 4 +- informasi_fasilitas/test_models.py | 6 +- informasi_fasilitas/test_views_komentar.py | 6 +- informasi_fasilitas/views.py | 10 +-- notification/test_utils.py | 75 +++++++------------ notification/test_views.py | 4 +- notification/utils.py | 52 ++++++------- 11 files changed, 106 insertions(+), 101 deletions(-) create mode 100644 informasi_fasilitas/migrations/0017_auto_20210531_1003.py diff --git a/informasi_fasilitas/admin.py b/informasi_fasilitas/admin.py index 608afb0..7e2232a 100644 --- a/informasi_fasilitas/admin.py +++ b/informasi_fasilitas/admin.py @@ -2,11 +2,11 @@ from django.contrib import admin, messages from django.utils.translation import gettext_lazy as _ from notification.utils import send_komentar_notification, get_target_fcm_device -from .models import Lokasi, Fasilitas, Komentar, Kegiatan, KomentarKegiatan, FotoKegiatan +from .models import Lokasi, Fasilitas, KomentarFasilitas, Kegiatan, KomentarKegiatan, FotoKegiatan class KomentarAdmin(admin.ModelAdmin): - list_display = ('__str__', 'deskripsi', 'user', 'notify_to') + list_display = ('__str__', 'user', 'created', 'notify_to') actions = ('send_notification',) def send_notification(self, request, queryset): @@ -30,7 +30,7 @@ class KomentarAdmin(admin.ModelAdmin): # Register your models here. admin.site.register(Lokasi) admin.site.register(Fasilitas) -admin.site.register(Komentar, KomentarAdmin) +admin.site.register(KomentarFasilitas, KomentarAdmin) admin.site.register(Kegiatan) admin.site.register(KomentarKegiatan, KomentarAdmin) admin.site.register(FotoKegiatan) diff --git a/informasi_fasilitas/migrations/0017_auto_20210531_1003.py b/informasi_fasilitas/migrations/0017_auto_20210531_1003.py new file mode 100644 index 0000000..7a899d0 --- /dev/null +++ b/informasi_fasilitas/migrations/0017_auto_20210531_1003.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1.7 on 2021-05-31 10:03 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('informasi_fasilitas', '0016_kegiatan_links'), + ] + + operations = [ + migrations.RenameModel( + old_name='Komentar', + new_name='KomentarFasilitas', + ), + migrations.RenameField( + model_name='komentarfasilitas', + old_name='date_time', + new_name='created', + ), + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index fe0c90d..8544288 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -89,14 +89,19 @@ class Kegiatan(models.Model): class Komentar(models.Model): objects = models.Manager() - fasilitas = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) - date_time = models.DateTimeField(auto_now_add=True) deskripsi = models.TextField() + created = models.DateTimeField(auto_now_add=True) + + class Meta: + abstract = True def __str__(self): return self.deskripsi +class KomentarFasilitas(Komentar): + fasilitas = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) + class Likes(models.Model): objects = models.Manager() user = models.ForeignKey(User, on_delete=models.CASCADE) @@ -109,15 +114,8 @@ class Dislikes(models.Model): fasilitas = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) -class KomentarKegiatan(models.Model): - objects = models.Manager() - user = models.ForeignKey(User, on_delete=models.CASCADE) +class KomentarKegiatan(Komentar): kegiatan = models.ForeignKey(Kegiatan, on_delete=models.CASCADE) - deskripsi = models.TextField() - created = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return self.deskripsi class FotoKegiatan(models.Model): objects = models.Manager() diff --git a/informasi_fasilitas/test_admin.py b/informasi_fasilitas/test_admin.py index 335a601..ca177ea 100644 --- a/informasi_fasilitas/test_admin.py +++ b/informasi_fasilitas/test_admin.py @@ -37,7 +37,7 @@ class TestAdmin(TestCase): def test_list_display(self): list_display = self.komentar_admin.get_list_display(request) - self.assertEqual(list_display, ('__str__', 'deskripsi', 'user', + self.assertEqual(list_display, ('__str__', 'user', 'created', 'notify_to')) def test_list_actions(self): diff --git a/informasi_fasilitas/test_base.py b/informasi_fasilitas/test_base.py index e481d3d..b0f4cfd 100644 --- a/informasi_fasilitas/test_base.py +++ b/informasi_fasilitas/test_base.py @@ -11,7 +11,7 @@ from django.core.files.base import ContentFile from .models import ( Lokasi, Fasilitas, - Komentar, + KomentarFasilitas, KURSI_RODA, Likes, Dislikes, @@ -177,7 +177,7 @@ class InformasiFasilitasTest(TestCase): fasilitas_dict=fasilitas_dict, ) - return Komentar.objects.create( + return KomentarFasilitas.objects.create( **komentar_dict, user=user, fasilitas=fasilitas, diff --git a/informasi_fasilitas/test_models.py b/informasi_fasilitas/test_models.py index 214c04e..e6cd550 100644 --- a/informasi_fasilitas/test_models.py +++ b/informasi_fasilitas/test_models.py @@ -1,7 +1,7 @@ from django.db.utils import IntegrityError from .test_base import InformasiFasilitasTest -from .models import (Lokasi, Fasilitas, Komentar, Likes, Dislikes, +from .models import (Lokasi, Fasilitas, KomentarFasilitas, Likes, Dislikes, Kegiatan, FotoKegiatan, KomentarKegiatan) @@ -49,13 +49,13 @@ class InformasiFasilitasModelTest(InformasiFasilitasTest): def test_models_komentar_not_created(self): with self.assertRaises(IntegrityError) as ex: - obj = Komentar(fasilitas=None) + obj = KomentarFasilitas(fasilitas=None) obj.save() self.assertEqual(ex.expected, IntegrityError) def test_models_create_new_komentar(self): self.create_komentar_test() - count = Komentar.objects.all().count() + count = KomentarFasilitas.objects.all().count() self.assertNotEqual(count, 0) def test_models_komentar_str(self): diff --git a/informasi_fasilitas/test_views_komentar.py b/informasi_fasilitas/test_views_komentar.py index b81ba1f..d5b596e 100644 --- a/informasi_fasilitas/test_views_komentar.py +++ b/informasi_fasilitas/test_views_komentar.py @@ -3,7 +3,7 @@ from http import HTTPStatus from django.urls import reverse from .test_base import InformasiFasilitasViewTest -from .models import Komentar +from .models import KomentarFasilitas from pplbackend.utils import response_decode from .views import TIME_FORMAT @@ -26,7 +26,7 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): self.client.post(self.add_komentar_url, self.mock_komentar_test) self.assertEqual(response.status_code, HTTPStatus.CREATED) - count = Komentar.objects.filter(fasilitas=self.fasilitas).count() + count = KomentarFasilitas.objects.filter(fasilitas=self.fasilitas).count() self.assertEqual(count, 1) def test_cannot_comment_facility_if_deskripsi_empty(self): @@ -48,7 +48,7 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): 'id': komentar.id, 'deskripsi': komentar.deskripsi, 'creator': komentar.user.last_name, - 'date_time': komentar.date_time.strftime(TIME_FORMAT), + 'created': komentar.created.strftime(TIME_FORMAT), } } self.assertEqual(response_json, expected_json) diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index a52ef9c..907b71a 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -9,7 +9,7 @@ from rest_framework import viewsets from notification.utils import send_komentar_notification from .serializers import LokasiSerializer -from .models import Lokasi, Fasilitas, Komentar, Likes, Dislikes +from .models import Lokasi, Fasilitas, KomentarFasilitas, Likes, Dislikes from .permissions import UserPermission TIME_FORMAT = "%d-%m-%Y %H:%M:%S" @@ -242,13 +242,13 @@ def add_komentar(request, place_id, id): lokasi = Lokasi.objects.get(place_id=place_id) fasilitas = Fasilitas.objects.get(lokasi=lokasi, id=id) deskripsi = request.POST['deskripsi'] - komentar = Komentar.objects.create(fasilitas=fasilitas, + komentar = KomentarFasilitas.objects.create(fasilitas=fasilitas, user=request.user, deskripsi=deskripsi) send_komentar_notification(komentar) return JsonResponse({'response': 'komentar added', 'id': komentar.id, - "created_date": komentar.date_time.strftime(TIME_FORMAT)}, status=HTTPStatus.CREATED) + "created_date": komentar.created.strftime(TIME_FORMAT)}, status=HTTPStatus.CREATED) except KeyError as e: return JsonResponse({'response': missing_key_message(str(e))}, status=HTTPStatus.BAD_REQUEST) except Exception as e: @@ -262,7 +262,7 @@ def list_komentar(request, place_id, id): try: lokasi = Lokasi.objects.get(place_id=place_id) fasilitas = Fasilitas.objects.get(lokasi=lokasi, id=id) - list_komentar = Komentar.objects.filter(fasilitas=fasilitas) + list_komentar = KomentarFasilitas.objects.filter(fasilitas=fasilitas) return_json = {} for komentar in list_komentar: return_json[komentar.id] = {} @@ -270,7 +270,7 @@ def list_komentar(request, place_id, id): komentar_details["id"] = komentar.id komentar_details["deskripsi"] = komentar.deskripsi komentar_details["creator"] = komentar.user.last_name - komentar_details["date_time"] = komentar.date_time.strftime( + komentar_details["created"] = komentar.created.strftime( TIME_FORMAT) return JsonResponse(return_json, status=HTTPStatus.OK) except KeyError as key: diff --git a/notification/test_utils.py b/notification/test_utils.py index 151857c..70fc25a 100644 --- a/notification/test_utils.py +++ b/notification/test_utils.py @@ -4,7 +4,7 @@ from django.db.models import QuerySet from django.contrib.auth.models import User from fcm_django.models import FCMDevice -from informasi_fasilitas.models import (Komentar, KomentarKegiatan, +from informasi_fasilitas.models import (KomentarFasilitas, KomentarKegiatan, Fasilitas, Kegiatan, Lokasi) from . import utils from .test_base import BaseTestNotification @@ -12,7 +12,7 @@ from .test_base import BaseTestNotification class TestUtilsNotification(BaseTestNotification): def test_get_type_informasi_from_komentar_should_return_fasilitas(self): - komentar = Komentar() + komentar = KomentarFasilitas() type_fasilitas = utils.get_type_informasi_from_komentar(komentar) self.assertEqual(type_fasilitas, 'fasilitas') @@ -23,7 +23,7 @@ class TestUtilsNotification(BaseTestNotification): def test_get_informasi_from_komentar_should_return_fasilitas(self): fasilitas = Fasilitas() - komentar = Komentar(fasilitas=fasilitas) + komentar = KomentarFasilitas(fasilitas=fasilitas) ret_fasilitas = utils.get_informasi(komentar) self.assertTrue(isinstance(ret_fasilitas, Fasilitas)) @@ -37,7 +37,7 @@ class TestUtilsNotification(BaseTestNotification): place_id = 'dummy_place_id' lokasi = Lokasi(place_id=place_id) fasilitas = Fasilitas(lokasi=lokasi) - komentar = Komentar(fasilitas=fasilitas) + komentar = KomentarFasilitas(fasilitas=fasilitas) ret_place_id = utils.get_place_id_from_komentar(komentar) @@ -56,7 +56,7 @@ class TestUtilsNotification(BaseTestNotification): def test_get_informasi_id_from_komentar_should_return_its_informasi_id(self): _id = 8 fasilitas = Fasilitas(id=_id) - komentar = Komentar(fasilitas=fasilitas) + komentar = KomentarFasilitas(fasilitas=fasilitas) ret_place_id = utils.get_informasi_id_from_komentar(komentar) @@ -73,7 +73,7 @@ class TestUtilsNotification(BaseTestNotification): def test_get_sender_last_name_from_komentar_not_null(self): user = User(last_name='ariqbasyar') - komentar = Komentar(user=user) + komentar = KomentarFasilitas(user=user) ret_last_name = utils.get_sender_last_name_from_komentar(komentar) @@ -89,7 +89,7 @@ class TestUtilsNotification(BaseTestNotification): fcmdevice.save() fasilitas = Fasilitas(user=user) - komentar = Komentar(fasilitas=fasilitas, user=user_sender) + komentar = KomentarFasilitas(fasilitas=fasilitas, user=user_sender) ret_fcm_device = utils.get_target_fcm_device(komentar) @@ -110,7 +110,7 @@ class TestUtilsNotification(BaseTestNotification): fcmdevice.save() fasilitas = Fasilitas(user=user) - komentar = Komentar(fasilitas=fasilitas, user=user_sender) + komentar = KomentarFasilitas(fasilitas=fasilitas, user=user_sender) ret_fcm_device = utils.get_target_fcm_device(komentar) @@ -124,64 +124,45 @@ class TestUtilsNotification(BaseTestNotification): fcmdevice.save() fasilitas = Fasilitas(user=user) - komentar = Komentar(fasilitas=fasilitas, user=user) + komentar = KomentarFasilitas(fasilitas=fasilitas, user=user) ret_fcm_device = utils.get_target_fcm_device(komentar) self.assertEqual(len(ret_fcm_device), 0) - def test_get_formatted_username_length_less_than_max_length_name_should_not_get_trimmed(self): - username = 'ariq' - ret_username = utils.get_formatted_user_name(username, max_length_name=10) - self.assertEqual(username, ret_username) + def test_get_formatted_message(self): + komentar = MagicMock() + komentar.deskripsi = 'ini deskripsi' + ret_formatted_message = utils.get_formatted_message(komentar) + self.assertEqual(ret_formatted_message, '"%s"' % komentar.deskripsi) - def test_get_formatted_username_length_equal_max_length_name_should_not_get_trimmed(self): - username = 'ariqbasyar' - ret_username = utils.get_formatted_user_name(username, max_length_name=10) - self.assertEqual(username, ret_username) - - def test_get_formatted_username_length_more_than_max_length_name_should_get_trimmed(self): - username = 'ariqbasyarr' - - max_length_name = 10 - replacement_str = '...' - length_replacement = len(replacement_str) - ret_username = utils.get_formatted_user_name(username, max_length_name=max_length_name, - replacement_str=replacement_str) - - self.assertEqual(ret_username, '%s%s' % (username[:max_length_name - length_replacement], - replacement_str)) - self.assertEqual(ret_username, 'ariqbas...') - - def test_get_formatted_message_username_should_not_get_trimmed(self): - username = 'ariqbasyar' - ret_formatted_message = utils.get_formatted_message(username) - self.assertEqual(ret_formatted_message, 'ariqbasyar menambahkan komentar baru') - - def test_get_formatted_message_username_should_get_trimmed(self): - username = 'ariqbasyarr' - ret_formatted_message = utils.get_formatted_message(username) - self.assertEqual(ret_formatted_message, 'ariqbas... menambahkan komentar baru') + def test_get_formatted_title(self): + username = 'dummy username' + ret_formatted_title = utils.get_formatted_title(username) + self.assertEqual(ret_formatted_title, 'dummy username menambahkan komentar baru') @patch('notification.utils.get_target_fcm_device') @patch('notification.utils.get_formatted_message') + @patch('notification.utils.get_formatted_title') @patch('notification.utils.get_sender_last_name_from_komentar') @patch('notification.utils.get_type_informasi_from_komentar') @patch('notification.utils.get_informasi_id_from_komentar') @patch('notification.utils.get_place_id_from_komentar') def test_send_komentar_notification(self, *args): - komentar = Komentar(deskripsi='dummy deskripsi') + komentar = KomentarFasilitas(deskripsi='dummy deskripsi') fcmdevice_mock = QuerySet(model=FCMDevice) fcmdevice_mock_return_value = (1,1,0,0,[]) fcmdevice_mock.send_message = MagicMock(return_value=fcmdevice_mock_return_value) - formatted_message = 'ariqbasyar menambahkan komentar baru' + formatted_title = 'ariqbasyar menambahkan komentar baru' + formatted_message = 'ini komentar' args[0].return_value = 'dummy place_id' args[1].return_value = 1 args[2].return_value = 'fasilitas' args[3].return_value = 'ariqbasyar' - args[4].return_value = formatted_message - args[5].return_value = fcmdevice_mock + args[4].return_value = formatted_title + args[5].return_value = formatted_message + args[6].return_value = fcmdevice_mock ret = utils.send_komentar_notification(komentar) @@ -192,13 +173,15 @@ class TestUtilsNotification(BaseTestNotification): args[3].assert_called_once_with(komentar) args[4].assert_called_once_with('ariqbasyar') args[5].assert_called_once_with(komentar) + args[6].assert_called_once_with(komentar) fcmdevice_mock.send_message.assert_called_once_with( - title=formatted_message, - body='"%s"' % komentar.deskripsi, + title=formatted_title, + body=formatted_message, data={ 'place_id': 'dummy place_id', 'id': 1, 'type': 'fasilitas', + 'title': formatted_title, 'message': formatted_message, } ) diff --git a/notification/test_views.py b/notification/test_views.py index 14e6afa..085e6c4 100644 --- a/notification/test_views.py +++ b/notification/test_views.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import User from fcm_django.models import FCMDevice, AbstractFCMDevice from fcm_django.api.rest_framework import FCMDeviceSerializer -from informasi_fasilitas.models import Lokasi, Kegiatan, Fasilitas, Komentar, KomentarKegiatan +from informasi_fasilitas.models import Lokasi, Kegiatan, Fasilitas, KomentarFasilitas, KomentarKegiatan from pplbackend.utils import get_client_login_with_user, response_decode from .test_base import BaseTestNotification @@ -178,7 +178,7 @@ class TestViewsNotification(BaseTestNotification): 'deskripsi': 'dummy deskripsi', }) data = response_decode(resp) - komentar = Komentar.objects.get(id=data['id']) + komentar = KomentarFasilitas.objects.get(id=data['id']) mock_notif.assert_called_once_with(komentar) diff --git a/notification/utils.py b/notification/utils.py index 1d7e503..a3bb283 100644 --- a/notification/utils.py +++ b/notification/utils.py @@ -1,14 +1,14 @@ from fcm_django.models import FCMDevice -from informasi_fasilitas.models import Komentar +from informasi_fasilitas.models import Komentar, KomentarFasilitas, KomentarKegiatan def get_type_informasi_from_komentar(komentar): - if isinstance(komentar, Komentar): + if isinstance(komentar, KomentarFasilitas): return 'fasilitas' return 'kegiatan' def get_informasi(komentar): - if isinstance(komentar, Komentar): + if isinstance(komentar, KomentarFasilitas): return komentar.fasilitas return komentar.kegiatan @@ -26,30 +26,30 @@ def get_target_fcm_device(komentar): .filter(user=get_informasi(komentar).user, active=True) .exclude(user=komentar.user)) -def get_formatted_user_name(name, max_length_name=10, replacement_str='...'): - length_replacement = len(replacement_str) - return name if len(name) <= max_length_name else '%s%s' % ( - name[:max_length_name - length_replacement], replacement_str - ) +def get_formatted_title(name): + return '%s menambahkan komentar baru' % name -def get_formatted_message(sender, **kwargs): - return '%s menambahkan komentar baru' % get_formatted_user_name(sender, **kwargs) +def get_formatted_message(komentar): + return '"%s"' % komentar.deskripsi def send_komentar_notification(komentar): - place_id = get_place_id_from_komentar(komentar) - informasi_id = get_informasi_id_from_komentar(komentar) - informasi_type = get_type_informasi_from_komentar(komentar) - sender = get_sender_last_name_from_komentar(komentar) - formatted_message = get_formatted_message(sender) target_fcm_device = get_target_fcm_device(komentar) - - return target_fcm_device.send_message( - title=formatted_message, - body='"%s"' % komentar.deskripsi, - data={ - 'place_id': place_id, - 'id': informasi_id, - 'type': informasi_type, - 'message': formatted_message, - } - ) + if target_fcm_device: + place_id = get_place_id_from_komentar(komentar) + informasi_id = get_informasi_id_from_komentar(komentar) + informasi_type = get_type_informasi_from_komentar(komentar) + sender = get_sender_last_name_from_komentar(komentar) + formatted_title = get_formatted_title(sender) + formatted_message = get_formatted_message(komentar) + + return target_fcm_device.send_message( + title=formatted_title, + body=formatted_message, + data={ + 'place_id': place_id, + 'id': informasi_id, + 'type': informasi_type, + 'title': formatted_title, + 'message': formatted_message, + } + ) -- GitLab From df5a470fc0830bbf6d63015bbef067377f162b55 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Mon, 31 May 2021 19:32:14 +0700 Subject: [PATCH 36/48] [REFACTOR] change models attribute from fasilitas/kegiatan to informasi on KomentarFasilitas and KomentarKegiatan --- .../migrations/0018_auto_20210531_1202.py | 23 +++++++++++++++++++ informasi_fasilitas/models.py | 4 ++-- informasi_fasilitas/serializers.py | 2 +- informasi_fasilitas/test_base.py | 10 ++++---- informasi_fasilitas/test_models.py | 2 +- informasi_fasilitas/test_views_komentar.py | 2 +- .../test_views_komentar_kegiatan.py | 16 ++++++------- informasi_fasilitas/views.py | 4 ++-- .../views_komentar_kegiatan.py | 10 ++++---- notification/test_utils.py | 18 +++++++-------- notification/utils.py | 6 ++--- 11 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 informasi_fasilitas/migrations/0018_auto_20210531_1202.py diff --git a/informasi_fasilitas/migrations/0018_auto_20210531_1202.py b/informasi_fasilitas/migrations/0018_auto_20210531_1202.py new file mode 100644 index 0000000..83474a8 --- /dev/null +++ b/informasi_fasilitas/migrations/0018_auto_20210531_1202.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.7 on 2021-05-31 12:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0017_auto_20210531_1003'), + ] + + operations = [ + migrations.RenameField( + model_name='komentarfasilitas', + old_name='fasilitas', + new_name='informasi', + ), + migrations.RenameField( + model_name='komentarkegiatan', + old_name='kegiatan', + new_name='informasi', + ), + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index 8544288..223011e 100644 --- a/informasi_fasilitas/models.py +++ b/informasi_fasilitas/models.py @@ -100,7 +100,7 @@ class Komentar(models.Model): return self.deskripsi class KomentarFasilitas(Komentar): - fasilitas = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) + informasi = models.ForeignKey(Fasilitas, on_delete=models.CASCADE) class Likes(models.Model): objects = models.Manager() @@ -115,7 +115,7 @@ class Dislikes(models.Model): created = models.DateTimeField(auto_now_add=True) class KomentarKegiatan(Komentar): - kegiatan = models.ForeignKey(Kegiatan, on_delete=models.CASCADE) + informasi = models.ForeignKey(Kegiatan, on_delete=models.CASCADE) class FotoKegiatan(models.Model): objects = models.Manager() diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 8b034ae..795e72c 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -48,7 +48,7 @@ class KegiatanSerializer(serializers.ModelSerializer): class KomentarKegiatanSerializer(serializers.ModelSerializer): - kegiatan = serializers.IntegerField(source='kegiatan.id', read_only=True) + kegiatan = serializers.IntegerField(source='informasi.id', read_only=True) creator = serializers.CharField(source='user.last_name', read_only=True) class Meta: model = KomentarKegiatan diff --git a/informasi_fasilitas/test_base.py b/informasi_fasilitas/test_base.py index b0f4cfd..ef9ce2b 100644 --- a/informasi_fasilitas/test_base.py +++ b/informasi_fasilitas/test_base.py @@ -180,7 +180,7 @@ class InformasiFasilitasTest(TestCase): return KomentarFasilitas.objects.create( **komentar_dict, user=user, - fasilitas=fasilitas, + informasi=fasilitas, ) def create_likes_test( @@ -284,19 +284,19 @@ class InformasiFasilitasTest(TestCase): user=None, komentar_kegiatan_dict=mock_komentar_kegiatan_test, user_dict=mock_user_test, - kegiatan=None + informasi=None ): user = self.get_or_create_user_test( user=user, user_dict=user_dict, ) - if kegiatan is None: - kegiatan = self.create_kegiatan_test() + if informasi is None: + informasi = self.create_kegiatan_test() return KomentarKegiatan.objects.create( **komentar_kegiatan_dict, - kegiatan=kegiatan, + informasi=informasi, user=user ) diff --git a/informasi_fasilitas/test_models.py b/informasi_fasilitas/test_models.py index e6cd550..9e929a5 100644 --- a/informasi_fasilitas/test_models.py +++ b/informasi_fasilitas/test_models.py @@ -49,7 +49,7 @@ class InformasiFasilitasModelTest(InformasiFasilitasTest): def test_models_komentar_not_created(self): with self.assertRaises(IntegrityError) as ex: - obj = KomentarFasilitas(fasilitas=None) + obj = KomentarFasilitas(informasi=None) obj.save() self.assertEqual(ex.expected, IntegrityError) diff --git a/informasi_fasilitas/test_views_komentar.py b/informasi_fasilitas/test_views_komentar.py index d5b596e..0dcc995 100644 --- a/informasi_fasilitas/test_views_komentar.py +++ b/informasi_fasilitas/test_views_komentar.py @@ -26,7 +26,7 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): self.client.post(self.add_komentar_url, self.mock_komentar_test) self.assertEqual(response.status_code, HTTPStatus.CREATED) - count = KomentarFasilitas.objects.filter(fasilitas=self.fasilitas).count() + count = KomentarFasilitas.objects.filter(informasi=self.fasilitas).count() self.assertEqual(count, 1) def test_cannot_comment_facility_if_deskripsi_empty(self): diff --git a/informasi_fasilitas/test_views_komentar_kegiatan.py b/informasi_fasilitas/test_views_komentar_kegiatan.py index c712e22..a78b78e 100644 --- a/informasi_fasilitas/test_views_komentar_kegiatan.py +++ b/informasi_fasilitas/test_views_komentar_kegiatan.py @@ -29,7 +29,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): self.client.post(self.add_komentar_kegiatan_url, self.mock_komentar_kegiatan_test) self.assertEqual(response.status_code, HTTPStatus.CREATED) - count = KomentarKegiatan.objects.filter(kegiatan=self.kegiatan).count() + count = KomentarKegiatan.objects.filter(informasi=self.kegiatan).count() self.assertEqual(count, 1) def test_fail_add_komentar_kegiatan(self): @@ -38,18 +38,18 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): self.client.post(self.add_komentar_kegiatan_url, None) self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) - count = KomentarKegiatan.objects.filter(kegiatan=self.kegiatan).count() + count = KomentarKegiatan.objects.filter(informasi=self.kegiatan).count() self.assertEqual(count, 0) def test_can_get_list_komentar_kegiatan(self): - komentar = self.create_komentar_kegiatan_test(user=self.user, kegiatan=self.kegiatan) + komentar = self.create_komentar_kegiatan_test(user=self.user, informasi=self.kegiatan) response = self.client.get(self.list_komentar_kegiatan_url) self.assertEqual(response.status_code, HTTPStatus.OK) response_json = response_decode(response) expected_id = komentar.id expected_creator = komentar.user.last_name - expected_kegiatan = komentar.kegiatan.id + expected_kegiatan = komentar.informasi.id expected_deskripsi = komentar.deskripsi self.assertEqual(response_json[str(komentar.id)]['id'], expected_id) @@ -59,7 +59,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(True, ('created' in response_json[str(komentar.id)].keys())) def test_can_get_komentar_kegiatan(self): - komentar = self.create_komentar_kegiatan_test(user=self.user, kegiatan=self.kegiatan) + komentar = self.create_komentar_kegiatan_test(user=self.user, informasi=self.kegiatan) kwargs_get_komentar_kegiatan = { 'place_id': self.lokasi.place_id, 'kegiatan_id': self.kegiatan.id, @@ -73,7 +73,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): response_json = response_decode(response) expected_id = komentar.id expected_creator = komentar.user.last_name - expected_kegiatan = komentar.kegiatan.id + expected_kegiatan = komentar.informasi.id expected_deskripsi = komentar.deskripsi self.assertEqual(response_json['id'], expected_id) @@ -88,7 +88,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): def test_can_delete_komentar_kegiatan(self): KomentarKegiatan.objects.all().delete() - komentar = self.create_komentar_kegiatan_test(user=self.user, kegiatan=self.kegiatan) + komentar = self.create_komentar_kegiatan_test(user=self.user, informasi=self.kegiatan) kwargs_delete_komentar_kegiatan = { 'place_id': self.lokasi.place_id, @@ -102,7 +102,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): response = self.client.delete(delete_komentar_kegiatan_url) self.assertEqual(response.status_code, HTTPStatus.OK) - count = KomentarKegiatan.objects.filter(kegiatan=self.kegiatan).count() + count = KomentarKegiatan.objects.filter(informasi=self.kegiatan).count() self.assertEqual(count, 0) def test_fail_delete_komentar_kegiatan(self): diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index 907b71a..57aedfa 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -242,7 +242,7 @@ def add_komentar(request, place_id, id): lokasi = Lokasi.objects.get(place_id=place_id) fasilitas = Fasilitas.objects.get(lokasi=lokasi, id=id) deskripsi = request.POST['deskripsi'] - komentar = KomentarFasilitas.objects.create(fasilitas=fasilitas, + komentar = KomentarFasilitas.objects.create(informasi=fasilitas, user=request.user, deskripsi=deskripsi) send_komentar_notification(komentar) @@ -262,7 +262,7 @@ def list_komentar(request, place_id, id): try: lokasi = Lokasi.objects.get(place_id=place_id) fasilitas = Fasilitas.objects.get(lokasi=lokasi, id=id) - list_komentar = KomentarFasilitas.objects.filter(fasilitas=fasilitas) + list_komentar = KomentarFasilitas.objects.filter(informasi=fasilitas) return_json = {} for komentar in list_komentar: return_json[komentar.id] = {} diff --git a/informasi_fasilitas/views_komentar_kegiatan.py b/informasi_fasilitas/views_komentar_kegiatan.py index af6718d..6dd2113 100644 --- a/informasi_fasilitas/views_komentar_kegiatan.py +++ b/informasi_fasilitas/views_komentar_kegiatan.py @@ -15,7 +15,7 @@ from .models import Kegiatan, KomentarKegiatan @permission_classes([]) def list_komentar_kegiatan(request, place_id, kegiatan_id): try: - queryset = KomentarKegiatan.objects.filter(kegiatan__lokasi__place_id=place_id, kegiatan__id=kegiatan_id) + queryset = KomentarKegiatan.objects.filter(informasi__lokasi__place_id=place_id, informasi__id=kegiatan_id) serializer = KomentarKegiatanSerializer(queryset, many=True) data_response = serializer.data new_dict = {item['id']: dict(item) for item in data_response} @@ -28,8 +28,8 @@ def list_komentar_kegiatan(request, place_id, kegiatan_id): @permission_classes([]) def get_komentar_kegiatan(request, place_id, kegiatan_id, komentar_id): try: - komentar = KomentarKegiatan.objects.get(kegiatan__lokasi__place_id=place_id, - kegiatan__id=kegiatan_id, id=komentar_id) + komentar = KomentarKegiatan.objects.get(informasi__lokasi__place_id=place_id, + informasi__id=kegiatan_id, id=komentar_id) serializer = KomentarKegiatanSerializer(komentar, many=False) return JsonResponse(serializer.data, status=HTTPStatus.OK) except ObjectDoesNotExist: @@ -44,7 +44,7 @@ def add_komentar_kegiatan(request, place_id, kegiatan_id): deskripsi = request.POST['deskripsi'] komentar = KomentarKegiatan.objects \ .create( - kegiatan=kegiatan, + informasi=kegiatan, user=request.user, deskripsi=deskripsi ) @@ -59,7 +59,7 @@ def add_komentar_kegiatan(request, place_id, kegiatan_id): @permission_classes([IsAuthenticated]) def delete_komentar_kegiatan(request, place_id, kegiatan_id, komentar_id): try: - komentar = KomentarKegiatan.objects.get(kegiatan__lokasi__place_id=place_id, kegiatan__id=kegiatan_id, id=komentar_id) + komentar = KomentarKegiatan.objects.get(informasi__lokasi__place_id=place_id, informasi__id=kegiatan_id, id=komentar_id) komentar.delete() return JsonResponse({'response': 'komentar kegiatan deleted', 'id': komentar_id}, status=HTTPStatus.OK) diff --git a/notification/test_utils.py b/notification/test_utils.py index 70fc25a..d97e033 100644 --- a/notification/test_utils.py +++ b/notification/test_utils.py @@ -23,13 +23,13 @@ class TestUtilsNotification(BaseTestNotification): def test_get_informasi_from_komentar_should_return_fasilitas(self): fasilitas = Fasilitas() - komentar = KomentarFasilitas(fasilitas=fasilitas) + komentar = KomentarFasilitas(informasi=fasilitas) ret_fasilitas = utils.get_informasi(komentar) self.assertTrue(isinstance(ret_fasilitas, Fasilitas)) def test_get_informasi_from_komentarkegiatan_should_return_kegiatan(self): kegiatan = Kegiatan() - komentar_kegiatan = KomentarKegiatan(kegiatan=kegiatan) + komentar_kegiatan = KomentarKegiatan(informasi=kegiatan) ret_kegiatan = utils.get_informasi(komentar_kegiatan) self.assertTrue(isinstance(ret_kegiatan, Kegiatan)) @@ -37,7 +37,7 @@ class TestUtilsNotification(BaseTestNotification): place_id = 'dummy_place_id' lokasi = Lokasi(place_id=place_id) fasilitas = Fasilitas(lokasi=lokasi) - komentar = KomentarFasilitas(fasilitas=fasilitas) + komentar = KomentarFasilitas(informasi=fasilitas) ret_place_id = utils.get_place_id_from_komentar(komentar) @@ -47,7 +47,7 @@ class TestUtilsNotification(BaseTestNotification): place_id = 'dummy_place_id' lokasi = Lokasi(place_id=place_id) kegiatan = Kegiatan(lokasi=lokasi) - komentar = KomentarKegiatan(kegiatan=kegiatan) + komentar = KomentarKegiatan(informasi=kegiatan) ret_place_id = utils.get_place_id_from_komentar(komentar) @@ -56,7 +56,7 @@ class TestUtilsNotification(BaseTestNotification): def test_get_informasi_id_from_komentar_should_return_its_informasi_id(self): _id = 8 fasilitas = Fasilitas(id=_id) - komentar = KomentarFasilitas(fasilitas=fasilitas) + komentar = KomentarFasilitas(informasi=fasilitas) ret_place_id = utils.get_informasi_id_from_komentar(komentar) @@ -65,7 +65,7 @@ class TestUtilsNotification(BaseTestNotification): def test_get_informasi_id_from_komentar_kegiatan_should_return_its_informasi_id(self): _id = 9 kegiatan = Kegiatan(id=_id) - komentar = KomentarKegiatan(kegiatan=kegiatan) + komentar = KomentarKegiatan(informasi=kegiatan) ret_place_id = utils.get_informasi_id_from_komentar(komentar) @@ -89,7 +89,7 @@ class TestUtilsNotification(BaseTestNotification): fcmdevice.save() fasilitas = Fasilitas(user=user) - komentar = KomentarFasilitas(fasilitas=fasilitas, user=user_sender) + komentar = KomentarFasilitas(informasi=fasilitas, user=user_sender) ret_fcm_device = utils.get_target_fcm_device(komentar) @@ -110,7 +110,7 @@ class TestUtilsNotification(BaseTestNotification): fcmdevice.save() fasilitas = Fasilitas(user=user) - komentar = KomentarFasilitas(fasilitas=fasilitas, user=user_sender) + komentar = KomentarFasilitas(informasi=fasilitas, user=user_sender) ret_fcm_device = utils.get_target_fcm_device(komentar) @@ -124,7 +124,7 @@ class TestUtilsNotification(BaseTestNotification): fcmdevice.save() fasilitas = Fasilitas(user=user) - komentar = KomentarFasilitas(fasilitas=fasilitas, user=user) + komentar = KomentarFasilitas(informasi=fasilitas, user=user) ret_fcm_device = utils.get_target_fcm_device(komentar) diff --git a/notification/utils.py b/notification/utils.py index a3bb283..d3dd9a7 100644 --- a/notification/utils.py +++ b/notification/utils.py @@ -1,6 +1,6 @@ from fcm_django.models import FCMDevice -from informasi_fasilitas.models import Komentar, KomentarFasilitas, KomentarKegiatan +from informasi_fasilitas.models import KomentarFasilitas def get_type_informasi_from_komentar(komentar): if isinstance(komentar, KomentarFasilitas): @@ -8,9 +8,7 @@ def get_type_informasi_from_komentar(komentar): return 'kegiatan' def get_informasi(komentar): - if isinstance(komentar, KomentarFasilitas): - return komentar.fasilitas - return komentar.kegiatan + return komentar.informasi def get_place_id_from_komentar(komentar): return get_informasi(komentar).lokasi.place_id -- GitLab From 08026b73abea7ab96ecdd366f55432def0b6b320 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Mon, 31 May 2021 19:42:18 +0700 Subject: [PATCH 37/48] [CHORE] makemigrations merge 0018 and 0019 --- .../migrations/0020_merge_20210531_1241.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 informasi_fasilitas/migrations/0020_merge_20210531_1241.py diff --git a/informasi_fasilitas/migrations/0020_merge_20210531_1241.py b/informasi_fasilitas/migrations/0020_merge_20210531_1241.py new file mode 100644 index 0000000..16c7bb8 --- /dev/null +++ b/informasi_fasilitas/migrations/0020_merge_20210531_1241.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.7 on 2021-05-31 12:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0018_auto_20210531_1202'), + ('informasi_fasilitas', '0019_auto_20210527_2251'), + ] + + operations = [ + ] -- GitLab From dc96d1cbab82264d678c0b98ab38e143f01c5049 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Tue, 1 Jun 2021 08:06:08 +0700 Subject: [PATCH 38/48] [CHORE] fix now image will not be deleted if FE send an empty string --- .../migrations/0021_auto_20210531_1529.py | 18 ++++++ informasi_fasilitas/test_views_kegiatan.py | 4 +- registrasi/serializers.py | 8 +++ registrasi/test_serializers.py | 58 +++++++++++++++++-- 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 informasi_fasilitas/migrations/0021_auto_20210531_1529.py diff --git a/informasi_fasilitas/migrations/0021_auto_20210531_1529.py b/informasi_fasilitas/migrations/0021_auto_20210531_1529.py new file mode 100644 index 0000000..3b13d22 --- /dev/null +++ b/informasi_fasilitas/migrations/0021_auto_20210531_1529.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.7 on 2021-05-31 15:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0020_merge_20210531_1241'), + ] + + operations = [ + migrations.AlterField( + model_name='kegiatan', + name='zona_waktu', + field=models.CharField(choices=[('WIB', 'WIB'), ('WITA', 'WITA'), ('WIT', 'WIT')], default='WIB', max_length=4), + ), + ] diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index cca1edc..8886276 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -107,7 +107,6 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'creator_email': self.mock_user_test['email'], '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() @@ -163,7 +162,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'id': self.kegiatan.id, 'place_id': self.kegiatan.lokasi.place_id, 'creator': self.kegiatan.user.last_name, - 'creator_email': kegiatan_wita.user.email, + 'creator_email': self.kegiatan.user.email, 'nama_kegiatan': self.kegiatan.nama_kegiatan, 'penyelenggara': self.kegiatan.penyelenggara, 'deskripsi': self.kegiatan.deskripsi, @@ -277,7 +276,6 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 'creator_email': self.mock_user_test['email'], 'time_start': self.kegiatan_time_start, 'time_end': self.kegiatan_time_end}) - }) send_data.pop("images") expected_json.update(send_data) diff --git a/registrasi/serializers.py b/registrasi/serializers.py index 6415db6..7fb0c51 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -59,6 +59,14 @@ class BisaGoUserSerializer(serializers.ModelSerializer): representation[hidden_field] = self.Meta.hidden_replacement_char return representation + def to_internal_value(self, data): + copied_data = data.copy() + if copied_data.get('foto') == '': + if self.instance.foto is not None: + copied_data['foto'] = self.instance.foto + internal_value = super().to_internal_value(copied_data) + return internal_value + def _update_instance(self, instance, validated_data, fields, mapper): for field in fields: mapped = mapper.get(field) or field diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index 9cc4547..a68224c 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -23,6 +23,20 @@ class MockRequest: return 'http:/dummy%s' % path +class MockEmptyImage: + def __init__(self, name, size=1): + self.name = name + self.size = size + # https://stackoverflow.com/a/26896684/11485041 + self.content = b"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E" + self._committed = True + + def __eq__(self, obj): + if isinstance(obj, str): + if obj == '': + return True + + class BaseTestSerializer(TestCase): mock_user = { 'username': 'dummy username', @@ -36,8 +50,9 @@ class BaseTestSerializer(TestCase): } def setUp(self): - self.image = SimpleUploadedFile("test1.jpg", - content=open("test_file/test1.jpg", 'rb').read(), content_type='image/jpeg') + self.image = open("test_file/test1.jpg", 'rb') + self.upload_image = SimpleUploadedFile("test1.jpg", + content=self.image.read(), content_type='upload_image/jpeg') self.hidden_fields = ['nomor telepon','email','alamat','tanggal lahir'] class TestBisaGoUserSerializer(BaseTestSerializer): @@ -147,7 +162,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): update = { 'name': 'another dummy name', 'phone_number': '99999999', - 'seen': False, 'alamat': 'dif dummy st', 'foto': self.image, + 'seen': False, 'alamat': 'dif dummy st', 'foto': self.upload_image, 'jenis_kelamin': 'laki-laki', 'tanggal_lahir': '2000-10-02', 'disabilitas': 'tanpa batas', 'pekerjaan': 'makan', 'organisasi_komunitas': 'komunitas tanpa batas' @@ -173,6 +188,39 @@ class TestBisaGoUserSerializer(BaseTestSerializer): self.assertEqual(data, expected) + def test_can_update_should_not_change_when_sending_empty_string_image(self): + user = User.objects.create_user(**self.mock_user) + bisagouser = BisaGoUser.objects.create(user=user, + **self.mock_bisagouser, foto=self.upload_image) + + update = { + 'name': 'another dummy name', 'phone_number': '99999999', + 'seen': False, 'alamat': 'dif dummy st', 'foto': MockEmptyImage('pic.jpg'), + 'jenis_kelamin': 'laki-laki', 'tanggal_lahir': '2000-10-02', + 'disabilitas': 'tanpa batas', 'pekerjaan': 'makan', + 'organisasi_komunitas': 'komunitas tanpa batas' + } + request = MockRequest(user_id=user.id) + + serializer = BisaGoUserSerializer(bisagouser, data=update, + context={'request':request}) + serializer.is_valid(raise_exception=True) + serializer.save() + + expected = { + 'phone_number': '99999999', 'tanggal_lahir': '2000-10-02', + 'jenis_kelamin': 'laki-laki', 'disabilitas': 'tanpa batas', + 'pekerjaan': 'makan', 'alamat': 'dif dummy st', 'seen': False, + 'username': 'dummy username', 'name': 'another dummy name', + 'email': 'test@email.com', 'hidden_fields': self.hidden_fields, + 'organisasi_komunitas': 'komunitas tanpa batas', + 'foto': MockRequest().build_absolute_uri(bisagouser.foto.url), + } + + data = serializer.data + + self.assertEqual(data, expected) + class TestRegisterUserSerializer(BaseTestSerializer): def test_meta_model(self): @@ -254,7 +302,7 @@ class TestRegisterUserSerializer(BaseTestSerializer): 'phone_number': '000011112222', 'tanggal_lahir': '2000-01-01', 'jenis_kelamin': 'laki-laki', - 'foto': self.image, + 'foto': self.upload_image, 'organisasi_komunitas': 'komunitas lain', } @@ -278,7 +326,7 @@ class TestRegisterUserSerializer(BaseTestSerializer): 'phone_number': '000011112222', 'tanggal_lahir': '2000-01-01', 'jenis_kelamin': 'laki-laki', - 'foto': self.image, + 'foto': self.upload_image, 'organisasi_komunitas': 'komunitas lain', } -- GitLab From d02db907f2127db3d2f7fe31ce41795f55a6968c Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Wed, 2 Jun 2021 20:49:03 +0700 Subject: [PATCH 39/48] [CHORES] fix bugs, new url authorize and fix models BisaGoUser, hidden field - fix has_obj_permission on BisaGoUserSerializer, now other than update and retrive has no object permission - added output and input formats for tanggal_lahir, output dd MM yyy, input dd MM yyyy and yyy-mm-dd - change hidden field replacement format with "({field_name} dirahasiakan)" - change return of login view from username, token, token_type to user (bisagouser object), token and token_type - new authorize url to check whether the request is authenticated or not --- new_rest_api/permissions.py | 6 ++- new_rest_api/test_permissions.py | 29 ++++++++++++- new_rest_api/test_views.py | 25 +++++++++-- new_rest_api/views.py | 10 ++++- oauth/tests.py | 26 +++++++----- oauth/views.py | 12 ++++-- .../migrations/0013_auto_20210601_0748.py | 18 ++++++++ .../migrations/0014_auto_20210601_0849.py | 28 +++++++++++++ .../migrations/0015_auto_20210601_1755.py | 21 ++++++++++ registrasi/models.py | 12 +++--- registrasi/serializers.py | 12 ++++-- registrasi/test_serializers.py | 42 +++++++++++++++++-- templates/acc_activate_success.html | 1 + 13 files changed, 204 insertions(+), 38 deletions(-) create mode 100644 registrasi/migrations/0013_auto_20210601_0748.py create mode 100644 registrasi/migrations/0014_auto_20210601_0849.py create mode 100644 registrasi/migrations/0015_auto_20210601_1755.py diff --git a/new_rest_api/permissions.py b/new_rest_api/permissions.py index 8666c40..918eb91 100644 --- a/new_rest_api/permissions.py +++ b/new_rest_api/permissions.py @@ -4,11 +4,13 @@ class UserViewPermission(permissions.BasePermission): def has_permission(self, request, view): if view.action in ['register', 'retrieve', 'activate']: return True - if view.action == 'update': + if view.action in ['update', 'authorize']: return request.user.is_authenticated return False def has_object_permission(self, request, view, obj): + if view.action == 'retrieve': + return True if view.action == 'update': return request.user.id == obj.user.id - return True + return False diff --git a/new_rest_api/test_permissions.py b/new_rest_api/test_permissions.py index 6569caa..e726f9c 100644 --- a/new_rest_api/test_permissions.py +++ b/new_rest_api/test_permissions.py @@ -38,6 +38,7 @@ class TestPermission(TestCase): self.view_retrieve = MockView('retrieve') self.view_update = MockView('update') self.view_delete = MockView('delete') + self.view_authorize = MockView('authorize') # has permission tests def test_permission_authenticated_cant_list(self): @@ -80,6 +81,16 @@ class TestPermission(TestCase): .has_permission(self.not_authenticated_request, self.view_delete) self.assertFalse(ret) + def test_permission_authenticated_can_authorize(self): + ret = self.user_view_permission\ + .has_permission(self.authenticated_request, self.view_authorize) + self.assertTrue(ret) + + def test_permission_authenticated_cant_authorize(self): + ret = self.user_view_permission\ + .has_permission(self.not_authenticated_request, self.view_authorize) + self.assertFalse(ret) + # has object permission tests def test_object_permission_can_update_same_user(self): user = MockUser(id=self.authenticated_request.user.id) @@ -95,9 +106,23 @@ class TestPermission(TestCase): .has_object_permission(self.authenticated_request, self.view_update, obj) self.assertFalse(ret) - def test_object_permission_other_action(self): + def test_object_permission_can_retrieve_same_user(self): + user = MockUser(id=self.authenticated_request.user.id) + obj = MockObject(user) + ret = self.user_view_permission\ + .has_object_permission(self.authenticated_request, self.view_retrieve, obj) + self.assertTrue(ret) + + def test_object_permission_can_retrieve_difference_user(self): user = MockUser(id=self.authenticated_request.user.id + 1) obj = MockObject(user) ret = self.user_view_permission\ - .has_object_permission(self.authenticated_request, self.view_list, obj) + .has_object_permission(self.authenticated_request, self.view_retrieve, obj) self.assertTrue(ret) + + def test_object_permission_other_action_forbidden(self): + user = MockUser(id=self.authenticated_request.user.id + 1) + obj = MockObject(user) + ret = self.user_view_permission\ + .has_object_permission(self.authenticated_request, self.view_list, obj) + self.assertFalse(ret) diff --git a/new_rest_api/test_views.py b/new_rest_api/test_views.py index e2c06a1..68615a2 100644 --- a/new_rest_api/test_views.py +++ b/new_rest_api/test_views.py @@ -6,7 +6,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.urls import reverse from django.test import TestCase -from pplbackend.utils import response_decode +from pplbackend.utils import response_decode, get_client_login_with_user class TestUserViews(TestCase): @@ -33,6 +33,8 @@ class TestUserViews(TestCase): 'foto': self.image, } + self.authorize_url = reverse('user-authorize') + @patch('registrasi.serializers.send_activation_email') def test_register_and_send_activation_email_is_called_and_user_inactive(self, mock_ativation_email): @@ -67,6 +69,18 @@ class TestUserViews(TestCase): self.assertEqual(data, expected) self.assertEqual(resp.status_code, status.BAD_REQUEST) + def test_authorize_authenticated(self): + client = get_client_login_with_user(self.user) + resp = client.get(self.authorize_url) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.content, b'') + + def test_authorize_not_authenticated(self): + resp = self.client.get(self.authorize_url) + self.assertEqual(resp.status_code, 401) + self.assertEqual(resp.content, + b'{"detail":"Authentication credentials were not provided."}') + @patch('new_rest_api.views.account_activation_token.check_token', return_value=True) @patch('new_rest_api.views.force_text') @patch('new_rest_api.views.urlsafe_base64_decode', return_value='someb64') @@ -88,7 +102,8 @@ class TestUserViews(TestCase): resp = self.client.get(activation_url) data = response_decode(resp) - expected = f''' + expected = f'''Aktivasi pengguna bisaGo + Hai {user.last_name},

@@ -127,7 +142,8 @@ bisaGo dev Team resp = self.client.get(activation_url) data = response_decode(resp) - expected = ''' + expected = '''Aktivasi pengguna bisaGo + Terjadi kesalahan atau akun sudah teraktivasi, mohon hubungi tim pengembang bisaGo. ''' @@ -158,7 +174,8 @@ Terjadi kesalahan atau akun sudah teraktivasi, mohon hubungi tim pengembang bisa resp = self.client.get(activation_url) data = response_decode(resp) - expected = ''' + expected = '''Aktivasi pengguna bisaGo + Terjadi kesalahan atau akun sudah teraktivasi, mohon hubungi tim pengembang bisaGo. ''' diff --git a/new_rest_api/views.py b/new_rest_api/views.py index f275220..b457fde 100644 --- a/new_rest_api/views.py +++ b/new_rest_api/views.py @@ -5,8 +5,10 @@ from django.utils.encoding import force_text from django.utils.http import urlsafe_base64_decode from rest_framework import viewsets +from rest_framework.authtoken.models import Token from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action +from rest_framework.exceptions import NotAuthenticated from rest_framework.response import Response from registrasi.models import BisaGoUser @@ -38,11 +40,15 @@ class UserViewSet(viewsets.ModelViewSet): 'email':instance.email, 'name':instance.last_name}, status=status.CREATED) - @action(detail=False, methods=['GET'], url_path=ACTIVATION_URL_REGEX, + @action(detail=False, url_path=ACTIVATION_URL_REGEX, renderer_classes=[AccountActivationHTMLRenderer]) - def activate(self, request, uidb64, token): + def activate(self, _, uidb64, token): return activate(uidb64, token) + @action(detail=False) + def authorize(self, _): + return Response() + def activate(uidb64, token): try: diff --git a/oauth/tests.py b/oauth/tests.py index 18fa166..931de91 100644 --- a/oauth/tests.py +++ b/oauth/tests.py @@ -1,12 +1,15 @@ -from django.test import TestCase import json -from .views import validate_google_token, _create_random_phone_number, _create_google_user + from unittest.mock import patch +from django.test import TestCase from django.test import Client -from registrasi.models import BisaGoUser from django.contrib.auth.models import User from rest_framework.exceptions import AuthenticationFailed +from pplbackend.utils import response_decode +from registrasi.models import BisaGoUser +from .views import validate_google_token, _create_random_phone_number, _create_google_user + class TestOauth(TestCase): def setUp(self): @@ -41,9 +44,10 @@ class TestOauth(TestCase): "access_token": "sankdsanlk", 'password': passcode }) - json_response = json.loads(response.content) + json_response = response_decode(response) + user = json_response.get('user') self.assertEqual(200, response.status_code) - self.assertEqual('mock_user@email.com', json_response.get("username")) + self.assertEqual('mock_user@email.com', user.get("username")) def test_request_token_email_not_exists(self): email = 'mock_user1212@email.com' @@ -96,9 +100,10 @@ class TestOauth(TestCase): 'password': passcode, 'google': True, }) - json_response = json.loads(response.content) + json_response = response_decode(response) + user = json_response.get('user') self.assertEqual(200, response.status_code) - self.assertEqual('mock_user@email.com', json_response.get("username")) + self.assertEqual('mock_user@email.com', user.get("username")) @patch('oauth.views.json.loads') @@ -113,9 +118,10 @@ class TestOauth(TestCase): 'password': passcode, 'google': True, }) - json_response = json.loads(response.content) + json_response = response_decode(response) + user = json_response.get('user') self.assertEqual(200, response.status_code) - self.assertEqual('mock_user4545@email.com', json_response.get("username")) + self.assertEqual('mock_user4545@email.com', user.get("username")) @patch('oauth.views.json.loads') def test_google_login_error(self, mock_json_loads): @@ -175,4 +181,4 @@ class TestOauth(TestCase): self.assertEqual('mock_user3434@email.com', user.username) self.assertEqual('mock_user3434@email.com', user.email) self.assertEqual('name', user.last_name) - self.assertEqual('1x1x1x1x1x1x1x1', user.phone_number.phone_number) + self.assertEqual('1x1x1x1x1x1x1x1', user.bisagouser.phone_number) diff --git a/oauth/views.py b/oauth/views.py index 43e1267..e9e943f 100644 --- a/oauth/views.py +++ b/oauth/views.py @@ -11,7 +11,9 @@ from django.contrib.auth.base_user import BaseUserManager from django.contrib.auth.hashers import make_password from django.contrib.auth.models import User from django.conf import settings + from registrasi.models import BisaGoUser +from registrasi.serializers import BisaGoUserSerializer @csrf_exempt @api_view(http_method_names=['POST']) @@ -28,7 +30,7 @@ def request_token(request): result = _check_normal_auth(request, email, password) user = result - return _check_user(user) + return _check_user(user, request) def _google_check(access_token, name): try: @@ -51,10 +53,12 @@ def _check_normal_auth(request, email, password): raise NotFound(detail="User doesn't exist") return user -def _check_user(user): +def _check_user(user, request): if user.is_active: - token, create = Token.objects.get_or_create(user=user) - response = {'username': user.username, 'token': token.key, 'token_type': "token"} + token, _ = Token.objects.get_or_create(user=user) + request.user = user + serializer = BisaGoUserSerializer(user.bisagouser, context={'request':request}) + response = {'user':serializer.data, 'token': token.key, 'token_type': "token"} return JsonResponse(response, status=200) else: raise AuthenticationFailed(detail="Please activate your account") diff --git a/registrasi/migrations/0013_auto_20210601_0748.py b/registrasi/migrations/0013_auto_20210601_0748.py new file mode 100644 index 0000000..391e57d --- /dev/null +++ b/registrasi/migrations/0013_auto_20210601_0748.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.7 on 2021-06-01 07:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0012_auto_20210528_1519'), + ] + + operations = [ + migrations.AlterField( + model_name='bisagouser', + name='organisasi_komunitas', + field=models.CharField(blank=True, default='-', max_length=64, null=True, verbose_name='Organisasi / Komunitas'), + ), + ] diff --git a/registrasi/migrations/0014_auto_20210601_0849.py b/registrasi/migrations/0014_auto_20210601_0849.py new file mode 100644 index 0000000..4413c5d --- /dev/null +++ b/registrasi/migrations/0014_auto_20210601_0849.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1.7 on 2021-06-01 08:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasi', '0013_auto_20210601_0748'), + ] + + operations = [ + migrations.AlterField( + model_name='bisagouser', + name='alamat', + field=models.CharField(blank=True, default='-', max_length=255, null=True, verbose_name='Alamat'), + ), + migrations.AlterField( + model_name='bisagouser', + name='disabilitas', + field=models.CharField(blank=True, default='Belum memilih', max_length=64, null=True, verbose_name='Disabilitas'), + ), + migrations.AlterField( + model_name='bisagouser', + name='pekerjaan', + field=models.CharField(blank=True, default='-', max_length=64, null=True, verbose_name='Pekerjaan'), + ), + ] diff --git a/registrasi/migrations/0015_auto_20210601_1755.py b/registrasi/migrations/0015_auto_20210601_1755.py new file mode 100644 index 0000000..45b788b --- /dev/null +++ b/registrasi/migrations/0015_auto_20210601_1755.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.7 on 2021-06-01 17:55 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('registrasi', '0014_auto_20210601_0849'), + ] + + operations = [ + migrations.AlterField( + model_name='bisagouser', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='bisagouser', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/registrasi/models.py b/registrasi/models.py index 1be6d6e..9867d9c 100644 --- a/registrasi/models.py +++ b/registrasi/models.py @@ -7,15 +7,15 @@ from pplbackend.custom_model_field import CompressedImageField class BisaGoUser(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="phone_number") + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="bisagouser") phone_number = models.CharField('Nomor Telepon', max_length=15, unique=True) tanggal_lahir = models.DateField('Tanggal Lahir', max_length=15, null=False, default=date.today) jenis_kelamin = models.CharField('Jenis Kelamin', max_length=20, default='-') - disabilitas = models.CharField('Disabilitas', max_length=64, null=True, default='Belum memilih') - organisasi_komunitas = models.CharField('Organisasi / Komunitas', max_length=64, null=True, default='-') - pekerjaan = models.CharField('Pekerjaan', max_length=64, default='-') - alamat = models.CharField('Alamat', max_length=255, default='-') - foto = CompressedImageField('Foto', upload_to='user/', blank=True, default=None, null=True, quality=55) + disabilitas = models.CharField('Disabilitas', max_length=64, null=True, blank=True, default='Belum memilih') + organisasi_komunitas = models.CharField('Organisasi / Komunitas', max_length=64, blank=True, null=True, default='-') + pekerjaan = models.CharField('Pekerjaan', max_length=64, blank=True, null=True, default='-') + alamat = models.CharField('Alamat', max_length=255, blank=True, null=True, default='-') + foto = CompressedImageField('Foto', upload_to='user/', blank=True, null=True, default=None, quality=55) seen = models.BooleanField('Seen', blank=True, default=True) class Meta: diff --git a/registrasi/serializers.py b/registrasi/serializers.py index 6415db6..905bf1d 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -11,6 +11,8 @@ class BisaGoUserSerializer(serializers.ModelSerializer): username = serializers.CharField(source='user.username', read_only=True) name = serializers.CharField(source='user.last_name') email = serializers.CharField(source='user.email', read_only=True) + tanggal_lahir = serializers.DateField(format='%d %B %Y', + input_formats=['%d %B %Y', '%Y-%m-%d']) hidden_fields = serializers.SerializerMethodField() @@ -27,7 +29,7 @@ class BisaGoUserSerializer(serializers.ModelSerializer): 'phone_number': 'nomor telepon', 'tanggal_lahir': 'tanggal lahir', } - hidden_replacement_char = '-' + hidden_replacement_char = '({} dirahasiakan)' update_bisago_fields = ( 'phone_number', 'seen', 'alamat', 'jenis_kelamin', 'tanggal_lahir', @@ -45,8 +47,7 @@ class BisaGoUserSerializer(serializers.ModelSerializer): def get_hidden_fields(self, *args): hidden_fields = self.Meta.hidden_fields verbose = self.Meta.hidden_fields_verbose - return [verbose.get(field) if verbose.get(field) else field\ - for field in hidden_fields] + return [verbose.get(field) or field for field in hidden_fields] def can_see_hidden_fields(self, instance): request = self.context['request'] @@ -56,7 +57,10 @@ class BisaGoUserSerializer(serializers.ModelSerializer): representation = super().to_representation(instance) if not self.can_see_hidden_fields(instance): for hidden_field in self.Meta.hidden_fields: - representation[hidden_field] = self.Meta.hidden_replacement_char + verbose_name = self.Meta.hidden_fields_verbose.get(hidden_field)\ + or hidden_field + representation[hidden_field] = self.Meta.hidden_replacement_char\ + .format(verbose_name) return representation def _update_instance(self, instance, validated_data, fields, mapper): diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index 9cc4547..8677a14 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -60,7 +60,8 @@ class TestBisaGoUserSerializer(BaseTestSerializer): {'phone_number': 'nomor telepon', 'tanggal_lahir': 'tanggal lahir'}) def test_meta_hidden_replacement_char(self): - self.assertEqual(BisaGoUserSerializer.Meta.hidden_replacement_char, '-') + self.assertEqual(BisaGoUserSerializer.Meta.hidden_replacement_char, + '({} dirahasiakan)') def test_meta_update_bisago_fields(self): self.assertEqual(BisaGoUserSerializer.Meta.update_bisago_fields, ( @@ -130,11 +131,11 @@ class TestBisaGoUserSerializer(BaseTestSerializer): replaced = BisaGoUserSerializer.Meta.hidden_replacement_char expected = { - 'phone_number': replaced, 'tanggal_lahir': replaced, + 'phone_number': '(nomor telepon dirahasiakan)', 'tanggal_lahir': '(tanggal lahir dirahasiakan)', 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': False, 'username': 'dummy username', 'name': 'dummy lastname', - 'email': replaced, 'alamat': replaced, + 'email': '(email dirahasiakan)', 'alamat': '(alamat dirahasiakan)', 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields, } @@ -160,7 +161,7 @@ class TestBisaGoUserSerializer(BaseTestSerializer): serializer.save() expected = { - 'phone_number': '99999999', 'tanggal_lahir': '2000-10-02', + 'phone_number': '99999999', 'tanggal_lahir': '02 October 2000', 'jenis_kelamin': 'laki-laki', 'disabilitas': 'tanpa batas', 'pekerjaan': 'makan', 'alamat': 'dif dummy st', 'seen': False, 'username': 'dummy username', 'name': 'another dummy name', @@ -173,6 +174,39 @@ class TestBisaGoUserSerializer(BaseTestSerializer): self.assertEqual(data, expected) + def test_can_update_should_not_change_when_sending_empty_string_image(self): + user = User.objects.create_user(**self.mock_user) + bisagouser = BisaGoUser.objects.create(user=user, + **self.mock_bisagouser, foto=self.upload_image) + + update = { + 'name': 'another dummy name', 'phone_number': '99999999', + 'seen': False, 'alamat': 'dif dummy st', 'foto': MockEmptyImage('pic.jpg'), + 'jenis_kelamin': 'laki-laki', 'tanggal_lahir': '2000-10-02', + 'disabilitas': 'tanpa batas', 'pekerjaan': 'makan', + 'organisasi_komunitas': 'komunitas tanpa batas' + } + request = MockRequest(user_id=user.id) + + serializer = BisaGoUserSerializer(bisagouser, data=update, + context={'request':request}) + serializer.is_valid(raise_exception=True) + serializer.save() + + expected = { + 'phone_number': '99999999', 'tanggal_lahir': '02 October 2000', + 'jenis_kelamin': 'laki-laki', 'disabilitas': 'tanpa batas', + 'pekerjaan': 'makan', 'alamat': 'dif dummy st', 'seen': False, + 'username': 'dummy username', 'name': 'another dummy name', + 'email': 'test@email.com', 'hidden_fields': self.hidden_fields, + 'organisasi_komunitas': 'komunitas tanpa batas', + 'foto': MockRequest().build_absolute_uri(bisagouser.foto.url), + } + + data = serializer.data + + self.assertEqual(data, expected) + class TestRegisterUserSerializer(BaseTestSerializer): def test_meta_model(self): diff --git a/templates/acc_activate_success.html b/templates/acc_activate_success.html index 8f61337..8ecf176 100644 --- a/templates/acc_activate_success.html +++ b/templates/acc_activate_success.html @@ -1,3 +1,4 @@ +Aktivasi pengguna bisaGo {% if success %} Hai {{ user.last_name }},
-- GitLab From 7a830061c9623c2401bfd180ef6724593a8a41fa Mon Sep 17 00:00:00 2001 From: Christopher Samuel Date: Thu, 3 Jun 2021 00:49:10 +0700 Subject: [PATCH 40/48] Pbi 14 agenda kegiatan --- informasi_fasilitas/test_views_kegiatan.py | 48 +++++++++++++++--- informasi_fasilitas/urls.py | 18 +++---- informasi_fasilitas/views_kegiatan.py | 57 ++++++++++++++-------- 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index 8886276..865e031 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -37,13 +37,10 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.kwargs_search_kegiatan_fail = {'query': 'this shouldnt exist', } self.kwargs_kegiatan_id = {'kegiatan_id': self.kegiatan.lokasi.place_id} - self.kwargs_list_kegiatan_in_order = { - 'start_index': 0, - 'query_limit': 10, - } - self.kwargs_list_kegiatan_in_order_fail = { - 'start_index': 10, - 'query_limit': 20, + self.kwargs_list_kegiatan_in_order = self.kwargs_search_kegiatan + self.kwargs_list_kegiatan_in_order_fail = self.kwargs_search_kegiatan_fail + self.kwargs_list_kegiatan_in_order_with_query = { + 'query': 'aaaa mock kegiatan', } image_path1, image_path2 = ("test_file/test1.jpg", "test_file/test2.jpg") @@ -59,6 +56,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): self.get_list_kegiatan_url = \ reverse('list-kegiatan', kwargs=self.kwargs_place_id) + self.get_list_all_kegiatan_url = \ + reverse('list-kegiatan-all') self.get_detail_kegiatan_url = \ reverse('detail-kegiatan', kwargs=self.kwargs_add_or_update_kegiatan) self.get_nearest_kegiatan_url = \ @@ -85,6 +84,8 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): reverse('list-kegiatan-by-name', kwargs=self.kwargs_list_kegiatan_in_order_fail) self.list_kegiatan_by_time_fail_url = \ reverse('list-kegiatan-by-time', kwargs=self.kwargs_list_kegiatan_in_order_fail) + self.list_kegiatan_by_name_with_query_url = \ + reverse('list-kegiatan-by-name', kwargs=self.kwargs_list_kegiatan_in_order_with_query) def tearDown(self): try: @@ -340,6 +341,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): def test_can_get_list_kegiatan_by_name_order(self): Kegiatan.objects.all().delete() + response_params = self.mock_kegiatan_test.copy() response = self.client.post(self.add_kegiatan_url, response_params) self.assertEqual(response.status_code, HTTPStatus.CREATED) @@ -371,6 +373,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): def test_can_get_list_kegiatan_by_time_order(self): Kegiatan.objects.all().delete() + response_params = self.mock_kegiatan_test.copy() response = self.client.post(self.add_kegiatan_url, response_params) self.assertEqual(response.status_code, HTTPStatus.CREATED) @@ -402,6 +405,7 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): def test_can_get_list_kegiatan_by_latest_added(self): Kegiatan.objects.all().delete() + response_params = self.mock_kegiatan_test.copy() response = self.client.post(self.add_kegiatan_url, response_params) self.assertEqual(response.status_code, HTTPStatus.CREATED) @@ -422,3 +426,33 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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) + + def test_can_get_list_all_kegiatan(self): + response = Client().get(self.get_list_all_kegiatan_url) + data = response.json() + self.assertEqual(len(data), 1) + + def test_can_get_list_kegiatan_by_name_order_with_query(self): + Kegiatan.objects.all().delete() + + response_params = self.mock_kegiatan_test.copy() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + + response_params = self.mock_kegiatan_name_order_test.copy() + response = self.client.post(self.add_kegiatan_url, response_params) + self.assertEqual(response.status_code, HTTPStatus.CREATED) + content = json.loads(response.content.decode('utf-8')) + ordered_name_mock_id = str(content['id']) + + response = self.client.get(self.list_kegiatan_by_name_with_query_url) + self.assertEqual(response.status_code, HTTPStatus.OK) + content = json.loads(response.content.decode('utf-8')) + + response_id_in_order = list(content.keys()) + + self.assertEqual(len(response_id_in_order), 1) + self.assertEqual(response_id_in_order[0], ordered_name_mock_id) + self.assertEqual(content[ordered_name_mock_id]['nama_kegiatan'], + self.mock_kegiatan_name_order_test['nama_kegiatan']) diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 61adcac..532e1ed 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -32,6 +32,9 @@ urlpatterns = [ path('lokasi/list-kegiatan//', views_kegiatan.list_kegiatan, name='list-kegiatan'), + + path('lokasi/list-kegiatan-all', + views_kegiatan.list_kegiatan_all, name='list-kegiatan-all'), path('lokasi/detail-kegiatan///', views_kegiatan.detail_kegiatan, name='detail-kegiatan'), @@ -39,9 +42,6 @@ urlpatterns = [ path('lokasi/kegiatan-terdekat', views_kegiatan.nearest_kegiatan, name='nearest-kegiatan'), - path('lokasi/search-kegiatan', - views_kegiatan.search_kegiatan, name='search-kegiatan'), - path('lokasi/search-kegiatan/', views_kegiatan.search_kegiatan, name='search-kegiatan'), @@ -54,22 +54,22 @@ urlpatterns = [ path('lokasi/list-foto-kegiatan//', views_kegiatan.list_foto_kegiatan, name='list-foto-kegiatan'), - path('lokasi/list-kegiatan-by-latest-added//', + path('lokasi/list-kegiatan-by-latest-added/', views_kegiatan.list_kegiatan_by_latest_added, name='list-kegiatan-by-latest-added'), - path('lokasi/list-kegiatan-by-latest-added/', + path('lokasi/list-kegiatan-by-latest-added', views_kegiatan.list_kegiatan_by_latest_added, name='list-kegiatan-by-latest-added'), - path('lokasi/list-kegiatan-by-name//', + path('lokasi/list-kegiatan-by-name/', views_kegiatan.list_kegiatan_by_name, name='list-kegiatan-by-name'), - path('lokasi/list-kegiatan-by-name/', + path('lokasi/list-kegiatan-by-name', views_kegiatan.list_kegiatan_by_name, name='list-kegiatan-by-name'), - path('lokasi/list-kegiatan-by-time//', + path('lokasi/list-kegiatan-by-time/', views_kegiatan.list_kegiatan_by_time, name='list-kegiatan-by-time'), - path('lokasi/list-kegiatan-by-time/', + path('lokasi/list-kegiatan-by-time', views_kegiatan.list_kegiatan_by_time, name='list-kegiatan-by-time'), path('lokasi/get-komentar-kegiatan///', diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 474f17b..9ccb138 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -11,6 +11,7 @@ from .models import Kegiatan, Lokasi, FotoKegiatan from django.contrib.auth.models import User from .serializers import KegiatanSerializer, KegiatanSerializerRead, FotoKegiatanSerializer +EMPTY_KEGIATAN = 'No Kegiatan available' @api_view(['GET']) @authentication_classes([]) @@ -23,6 +24,17 @@ def list_kegiatan(request, place_id): return JsonResponse(new_dict, status=HTTPStatus.OK) +@api_view(['GET']) +@authentication_classes([]) +@permission_classes([]) +def list_kegiatan_all(request): + queryset = _filter_kegiatan() + 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) + + @api_view(['GET']) @authentication_classes([]) @permission_classes([]) @@ -42,7 +54,7 @@ def nearest_kegiatan(request): time_now = timezone.now() queryset = Kegiatan.objects.filter(time_start__gte=time_now).order_by('time_start').first() if queryset is None: - raise NotFound(detail="No Kegiatan available") + raise NotFound(detail=EMPTY_KEGIATAN) serializer = KegiatanSerializerRead(queryset, many=False) return JsonResponse(_clean_json_kegiatan(serializer.data), status=HTTPStatus.OK) @@ -98,13 +110,10 @@ def list_foto_kegiatan(request, place_id, kegiatan_id): @api_view(['GET']) @authentication_classes([]) @permission_classes([]) -def search_kegiatan(request, query=''): - query_by_nama = Kegiatan.objects.filter(nama_kegiatan__icontains=query) - query_by_deskripsi = Kegiatan.objects.filter(deskripsi__icontains=query) - query_by_penyelenggara = Kegiatan.objects.filter(penyelenggara__icontains=query) - queryset = query_by_nama | query_by_deskripsi | query_by_penyelenggara +def search_kegiatan(request, query): + queryset = _filter_kegiatan(query) if not queryset.exists(): - raise NotFound(detail="No Kegiatan available") + raise NotFound(detail=EMPTY_KEGIATAN) 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) @@ -112,12 +121,12 @@ def search_kegiatan(request, query=''): @api_view(['GET']) @authentication_classes([]) @permission_classes([]) -def list_kegiatan_by_name(request, start_index, query_limit=9): +def list_kegiatan_by_name(request, query=None): time_now = timezone.now() - queryset = Kegiatan.objects.filter(time_end__gte=time_now).order_by('nama_kegiatan') - queryset = queryset[start_index:start_index+query_limit] + queryset = _filter_kegiatan(query) \ + .filter(time_end__gte=time_now).order_by('nama_kegiatan') if not queryset.exists(): - raise NotFound(detail="No Kegiatan available") + raise NotFound(detail=EMPTY_KEGIATAN) 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) @@ -125,12 +134,12 @@ def list_kegiatan_by_name(request, start_index, query_limit=9): @api_view(['GET']) @authentication_classes([]) @permission_classes([]) -def list_kegiatan_by_time(request, start_index, query_limit=9): +def list_kegiatan_by_time(request, query=None): time_now = timezone.now() - queryset = Kegiatan.objects.filter(time_end__gte=time_now).order_by('time_start') - queryset = queryset[start_index:start_index+query_limit] + queryset = _filter_kegiatan(query) \ + .filter(time_end__gte=time_now).order_by('time_start') if not queryset.exists(): - raise NotFound(detail="No Kegiatan available") + raise NotFound(detail=EMPTY_KEGIATAN) 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) @@ -138,12 +147,12 @@ def list_kegiatan_by_time(request, start_index, query_limit=9): @api_view(['GET']) @authentication_classes([]) @permission_classes([]) -def list_kegiatan_by_latest_added(request, start_index, query_limit=9): +def list_kegiatan_by_latest_added(request, query=None): time_now = timezone.now() - queryset = Kegiatan.objects.filter(time_end__gte=time_now).order_by('-id') - queryset = queryset[start_index:start_index+query_limit] + queryset = _filter_kegiatan(query) \ + .filter(time_end__gte=time_now).order_by('-id') if not queryset.exists(): - raise NotFound(detail="No Kegiatan available") + raise NotFound(detail=EMPTY_KEGIATAN) 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) @@ -166,3 +175,13 @@ def _create_list_kegiatan_foto(kegiatan, list_image): foto = FotoKegiatan.objects.create(kegiatan=kegiatan, foto=image) list_kegiatan_foto.append(foto) return list_kegiatan_foto + + +def _filter_kegiatan(query=None): + if query is None: + return Kegiatan.objects.all() + else: + query_by_nama = Kegiatan.objects.filter(nama_kegiatan__icontains=query) + query_by_deskripsi = Kegiatan.objects.filter(deskripsi__icontains=query) + query_by_penyelenggara = Kegiatan.objects.filter(penyelenggara__icontains=query) + return query_by_nama | query_by_deskripsi | query_by_penyelenggara -- GitLab From 1f7fd60f0943ef53fa5cff41c78622f5fb3c0361 Mon Sep 17 00:00:00 2001 From: Muhammad Rafif Elfazri Date: Thu, 3 Jun 2021 11:05:28 +0700 Subject: [PATCH 41/48] Pbi 12 info kegiatan --- .../migrations/0020_auto_20210601_0503.py | 22 +++ .../migrations/0021_auto_20210601_0631.py | 23 +++ .../migrations/0022_merge_20210602_1726.py | 14 ++ informasi_fasilitas/models.py | 10 + informasi_fasilitas/serializers.py | 72 ++++++- informasi_fasilitas/test_update_place_id.py | 5 +- informasi_fasilitas/test_views_fasilitas.py | 3 +- informasi_fasilitas/test_views_kegiatan.py | 62 +++++- informasi_fasilitas/urls.py | 6 + informasi_fasilitas/views.py | 177 ++++++------------ informasi_fasilitas/views_kegiatan.py | 42 ++++- 11 files changed, 300 insertions(+), 136 deletions(-) create mode 100644 informasi_fasilitas/migrations/0020_auto_20210601_0503.py create mode 100644 informasi_fasilitas/migrations/0021_auto_20210601_0631.py create mode 100644 informasi_fasilitas/migrations/0022_merge_20210602_1726.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/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/migrations/0022_merge_20210602_1726.py b/informasi_fasilitas/migrations/0022_merge_20210602_1726.py new file mode 100644 index 0000000..ecbdc4d --- /dev/null +++ b/informasi_fasilitas/migrations/0022_merge_20210602_1726.py @@ -0,0 +1,14 @@ +# Generated by Django 3.1.7 on 2021-06-02 17:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('informasi_fasilitas', '0021_auto_20210531_1529'), + ('informasi_fasilitas', '0021_auto_20210601_0631'), + ] + + operations = [ + ] diff --git a/informasi_fasilitas/models.py b/informasi_fasilitas/models.py index 02cf4c9..bad7bb4 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() @@ -116,15 +119,22 @@ 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(Komentar): informasi = models.ForeignKey(Kegiatan, on_delete=models.CASCADE) + class FotoKegiatan(models.Model): objects = models.Manager() kegiatan = models.ForeignKey(Kegiatan, related_name="images", on_delete=models.CASCADE) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 2777ffc..4f11555 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers -from .models import Lokasi, Kegiatan, KomentarKegiatan, FotoKegiatan +from .models import Lokasi, Fasilitas, Kegiatan, \ + KomentarKegiatan, FotoKegiatan, Likes, Dislikes import pytz TIMEZONE_INDONESIA = { @@ -33,11 +34,76 @@ class LokasiSerializer(serializers.ModelSerializer): } -class FotoKegiatanSerializer(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 = FotoKegiatan + model = FasilitasSerializer.Meta.model + 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) + + class Meta: + model = FotoKegiatan + fields = ("id", "kegiatan", "place_id", "foto") extra_kwargs = { 'foto': {'required': True}, 'kegiatan': {'required': True}, 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): 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) diff --git a/informasi_fasilitas/test_views_kegiatan.py b/informasi_fasilitas/test_views_kegiatan.py index 865e031..ffe50fd 100644 --- a/informasi_fasilitas/test_views_kegiatan.py +++ b/informasi_fasilitas/test_views_kegiatan.py @@ -33,6 +33,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', } @@ -49,11 +50,19 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): image2 = SimpleUploadedFile("test2.jpg", content=open(image_path2, 'rb').read(), content_type='image/jpeg') - self.kegiatan_images = {'images': [image1, image2]} - for image in self.kegiatan_images["images"]: - FotoKegiatan.objects.create(kegiatan=self.kegiatan, foto=image) + self.kegiatan_images = {'images[]': [image1, image2]} + self.foto = None + for image in self.kegiatan_images["images[]"]: + 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_list_all_kegiatan_url = \ @@ -62,16 +71,25 @@ class KegiatanRelatedViewTest(InformasiFasilitasViewTest): 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 = \ @@ -278,7 +296,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) @@ -312,6 +330,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) diff --git a/informasi_fasilitas/urls.py b/informasi_fasilitas/urls.py index 532e1ed..f03b11a 100644 --- a/informasi_fasilitas/urls.py +++ b/informasi_fasilitas/urls.py @@ -54,6 +54,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.py b/informasi_fasilitas/views.py index 16b2d9d..6683ce8 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -1,14 +1,17 @@ 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 +from .serializers import LokasiSerializer, FasilitasSerializerRead,\ + FasilitasSerializerWrite, LikesSerializer, DislikesSerializer from .models import Lokasi, Fasilitas, KomentarFasilitas, Likes, Dislikes from .permissions import UserPermission @@ -56,31 +59,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) @@ -91,34 +73,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) @@ -128,28 +104,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 +117,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) @@ -201,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) diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 9ccb138..86c6d04 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -67,12 +67,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: @@ -86,11 +86,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: @@ -107,6 +107,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 409a0ccc08c895492fe50dc9bb902d647f525757 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Fri, 4 Jun 2021 07:45:07 +0700 Subject: [PATCH 42/48] [CHORE] added creator_picture field for detail komentar --- informasi_fasilitas/serializers.py | 3 ++- informasi_fasilitas/test_views_komentar.py | 15 ++++++++++++++- .../test_views_komentar_kegiatan.py | 11 +++++------ informasi_fasilitas/views.py | 1 + registrasi/test_serializers.py | 6 +++--- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index 2777ffc..96d0f79 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -88,11 +88,12 @@ class KomentarKegiatanSerializer(serializers.ModelSerializer): kegiatan = serializers.IntegerField(source='informasi.id', read_only=True) creator = serializers.CharField(source='user.last_name', read_only=True) creator_email = serializers.CharField(source='user.email', read_only=True) + creator_picture = serializers.ImageField(source='user.bisagouser.foto', read_only=True) class Meta: model = KomentarKegiatan fields = ('id', 'creator', 'kegiatan', 'deskripsi', 'created', - 'creator_email') + 'creator_email', 'creator_picture') extra_kwargs = { 'deskripsi': {'required': True}, 'created': {'required' : True} diff --git a/informasi_fasilitas/test_views_komentar.py b/informasi_fasilitas/test_views_komentar.py index 59cf644..8b94eac 100644 --- a/informasi_fasilitas/test_views_komentar.py +++ b/informasi_fasilitas/test_views_komentar.py @@ -1,10 +1,12 @@ from http import HTTPStatus from django.urls import reverse +from django.core.files.uploadedfile import SimpleUploadedFile +from django.core.files import File from pplbackend.utils import response_decode +from registrasi.models import BisaGoUser from .test_base import InformasiFasilitasViewTest from .models import KomentarFasilitas -from pplbackend.utils import response_decode from .views import TIME_FORMAT @@ -20,6 +22,9 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): self.list_komentar_url = \ reverse('list-komentar', kwargs=self.kwargs_add_or_list_komentar) + self.image_path = "test_file/test1.jpg" + self.image = SimpleUploadedFile("test1.jpg", content=open(self.image_path, 'rb').read(), + content_type='image/jpeg') def test_can_comment_facility(self): response = \ @@ -38,11 +43,17 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(expected_json, response_json) def test_get_list_komentar(self): + bisagouser = BisaGoUser.objects.create(user=self.user, + phone_number='0'*12, foto=self.image) + self.user.bisagouser = bisagouser + self.user.save() komentar = self.create_komentar_test(fasilitas=self.fasilitas) response = self.client.get(self.list_komentar_url) self.assertEqual(response.status_code, HTTPStatus.OK) response_json = response_decode(response) + creator_picture = response_json[str(komentar.id)].pop('creator_picture')\ + if isinstance(response_json, dict) else '' expected_json = { str(komentar.id): { 'id': komentar.id, @@ -53,3 +64,5 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): } } self.assertEqual(response_json, expected_json) + self.assertRegex(creator_picture, + r'http://testserver/media/user/test1[0-9a-zA-Z_-]+.jpg') diff --git a/informasi_fasilitas/test_views_komentar_kegiatan.py b/informasi_fasilitas/test_views_komentar_kegiatan.py index a78b78e..abc7e61 100644 --- a/informasi_fasilitas/test_views_komentar_kegiatan.py +++ b/informasi_fasilitas/test_views_komentar_kegiatan.py @@ -31,7 +31,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): count = KomentarKegiatan.objects.filter(informasi=self.kegiatan).count() self.assertEqual(count, 1) - + def test_fail_add_komentar_kegiatan(self): KomentarKegiatan.objects.all().delete() response = \ @@ -40,7 +40,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): count = KomentarKegiatan.objects.filter(informasi=self.kegiatan).count() self.assertEqual(count, 0) - + def test_can_get_list_komentar_kegiatan(self): komentar = self.create_komentar_kegiatan_test(user=self.user, informasi=self.kegiatan) response = self.client.get(self.list_komentar_kegiatan_url) @@ -81,7 +81,7 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): self.assertEqual(response_json['kegiatan'], expected_kegiatan) self.assertEqual(response_json['deskripsi'], expected_deskripsi) self.assertEqual(True, ('created' in response_json.keys())) - + def test_fail_get_komentar_kegiatan(self): response = self.client.get('/informasi-fasilitas/lokasi/get-komentar-kegiatan/harusnyaTidakAda/101/1011') self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) @@ -98,14 +98,13 @@ class KomentarKegiatanRelatedViewTest(InformasiFasilitasViewTest): delete_komentar_kegiatan_url = \ reverse('delete-komentar-kegiatan', kwargs=kwargs_delete_komentar_kegiatan) - + response = self.client.delete(delete_komentar_kegiatan_url) self.assertEqual(response.status_code, HTTPStatus.OK) count = KomentarKegiatan.objects.filter(informasi=self.kegiatan).count() self.assertEqual(count, 0) - + def test_fail_delete_komentar_kegiatan(self): response = self.client.delete('/informasi-fasilitas/lokasi/delete-komentar-kegiatan/harusnyaTidakAda/10/10101') self.assertEqual(response.status_code, HTTPStatus.NOT_FOUND) - diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index 16b2d9d..53a851b 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -271,6 +271,7 @@ def list_komentar(request, place_id, id): komentar_details["deskripsi"] = komentar.deskripsi komentar_details["creator"] = komentar.user.last_name komentar_details["creator_email"] = komentar.user.email + komentar_details["creator_picture"] = request.build_absolute_uri(komentar.user.bisagouser.foto.url) komentar_details["created"] = komentar.created.strftime( TIME_FORMAT) return JsonResponse(return_json, status=HTTPStatus.OK) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index 5fa1917..c7734a8 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -144,11 +144,11 @@ class TestBisaGoUserSerializer(BaseTestSerializer): bisagouser = BisaGoUserSerializer(bisagouser, context={'request':request}) - replaced = BisaGoUserSerializer.Meta.hidden_replacement_char expected = { - 'phone_number': '(nomor telepon dirahasiakan)', 'tanggal_lahir': '(tanggal lahir dirahasiakan)', + 'phone_number': '(nomor telepon dirahasiakan)', + 'tanggal_lahir': '(tanggal lahir dirahasiakan)', 'jenis_kelamin': '-', 'disabilitas': 'Belum memilih', - 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': False, + 'pekerjaan': '-', 'foto': None, 'seen': False, 'username': 'dummy username', 'name': 'dummy lastname', 'email': '(email dirahasiakan)', 'alamat': '(alamat dirahasiakan)', 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields, -- GitLab From 4abf5ac884e2706cfe2e22d5c29a35f0c7e2f77d Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Fri, 4 Jun 2021 07:50:18 +0700 Subject: [PATCH 43/48] [CHORE] fix regex matching on komentar creator's image test --- informasi_fasilitas/test_views_komentar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/informasi_fasilitas/test_views_komentar.py b/informasi_fasilitas/test_views_komentar.py index 8b94eac..458eaa0 100644 --- a/informasi_fasilitas/test_views_komentar.py +++ b/informasi_fasilitas/test_views_komentar.py @@ -65,4 +65,4 @@ class KomentarRelatedViewTest(InformasiFasilitasViewTest): } self.assertEqual(response_json, expected_json) self.assertRegex(creator_picture, - r'http://testserver/media/user/test1[0-9a-zA-Z_-]+.jpg') + r'http://testserver/media/user/test1[0-9a-zA-Z_-]*.jpg') -- GitLab From 4eedf091fa069d28784cac1d52c1599718f8aa5e Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Fri, 4 Jun 2021 17:37:38 +0700 Subject: [PATCH 44/48] [RED] test for new read only field on bisagouser - new field: 'hidden_fields_verbose', 'can_see_hidden_fields', and 'hidden_fields_color' --- registrasi/test_serializers.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/registrasi/test_serializers.py b/registrasi/test_serializers.py index c7734a8..06a9b35 100644 --- a/registrasi/test_serializers.py +++ b/registrasi/test_serializers.py @@ -53,7 +53,9 @@ class BaseTestSerializer(TestCase): self.image = open("test_file/test1.jpg", 'rb') self.upload_image = SimpleUploadedFile("test1.jpg", content=self.image.read(), content_type='upload_image/jpeg') - self.hidden_fields = ['nomor telepon','email','alamat','tanggal lahir'] + self.hidden_fields = ('phone_number','email','alamat','tanggal_lahir') + self.hidden_fields_verbose = ['nomor telepon','email','alamat','tanggal lahir'] + self.maxDiff = 1e5 class TestBisaGoUserSerializer(BaseTestSerializer): def test_meta_model(self): @@ -63,7 +65,8 @@ class TestBisaGoUserSerializer(BaseTestSerializer): self.assertSequenceEqual(BisaGoUserSerializer.Meta.fields, ( 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'name', 'email', 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', - 'organisasi_komunitas', 'hidden_fields', + 'organisasi_komunitas', 'hidden_fields', 'hidden_fields_verbose', + 'can_see_hidden_fields', 'hidden_fields_color', )) def test_meta_hidden_fields(self): @@ -109,7 +112,9 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'pekerjaan': '-', 'alamat': '-', 'foto': None, 'seen': True, 'username': 'dummy username', 'name': 'dummy lastname', 'email': 'test@email.com', 'alamat': 'Dummy St', - 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields + 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields, + 'hidden_fields_verbose': self.hidden_fields_verbose, + 'can_see_hidden_fields': True, 'hidden_fields_color': '#808080', } self.assertEqual(bisagouser.data, expected) @@ -129,6 +134,8 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'username': 'dummy username', 'name': 'dummy lastname', 'email': 'test@email.com', 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields, + 'hidden_fields_verbose': self.hidden_fields_verbose, + 'can_see_hidden_fields': True, 'hidden_fields_color': '#808080', } self.assertEqual(bisagouser.data, expected) @@ -152,9 +159,11 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'username': 'dummy username', 'name': 'dummy lastname', 'email': '(email dirahasiakan)', 'alamat': '(alamat dirahasiakan)', 'organisasi_komunitas': '-', 'hidden_fields': self.hidden_fields, + 'hidden_fields_verbose': self.hidden_fields_verbose, + 'can_see_hidden_fields': False, 'hidden_fields_color': '#808080', } - self.assertEqual(bisagouser.data, expected) + self.assertDictEqual(bisagouser.data, expected) def test_can_update(self): user = User.objects.create_user(**self.mock_user) @@ -181,7 +190,9 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'pekerjaan': 'makan', 'alamat': 'dif dummy st', 'seen': False, 'username': 'dummy username', 'name': 'another dummy name', 'email': 'test@email.com', 'hidden_fields': self.hidden_fields, - 'organisasi_komunitas': 'komunitas tanpa batas' + 'organisasi_komunitas': 'komunitas tanpa batas', + 'hidden_fields_verbose': self.hidden_fields_verbose, + 'can_see_hidden_fields': True, 'hidden_fields_color': '#808080', } data = serializer.data @@ -216,11 +227,13 @@ class TestBisaGoUserSerializer(BaseTestSerializer): 'email': 'test@email.com', 'hidden_fields': self.hidden_fields, 'organisasi_komunitas': 'komunitas tanpa batas', 'foto': MockRequest().build_absolute_uri(bisagouser.foto.url), + 'hidden_fields_verbose': self.hidden_fields_verbose, + 'can_see_hidden_fields': True, 'hidden_fields_color': '#808080', } data = serializer.data - self.assertEqual(data, expected) + self.assertDictEqual(data, expected) class TestRegisterUserSerializer(BaseTestSerializer): -- GitLab From c7f85f9129d6bedeb221b2306e39f497cd545f95 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Fri, 4 Jun 2021 17:45:44 +0700 Subject: [PATCH 45/48] [GREEN] implemented new read only field on bisagouser and profile picture of komentar's creator - new field: 'hidden_fields_verbose', 'can_see_hidden_fields', and 'hidden_fields_color' --- informasi_fasilitas/views.py | 8 +++++++- registrasi/serializers.py | 22 ++++++++++++++++++---- requirements.txt | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/informasi_fasilitas/views.py b/informasi_fasilitas/views.py index 53a851b..3526800 100644 --- a/informasi_fasilitas/views.py +++ b/informasi_fasilitas/views.py @@ -271,7 +271,13 @@ def list_komentar(request, place_id, id): komentar_details["deskripsi"] = komentar.deskripsi komentar_details["creator"] = komentar.user.last_name komentar_details["creator_email"] = komentar.user.email - komentar_details["creator_picture"] = request.build_absolute_uri(komentar.user.bisagouser.foto.url) + foto = komentar.user.bisagouser.foto + try: + url = foto.url + except ValueError: + url = None + komentar_details["creator_picture"] =\ + request.build_absolute_uri(url) if url else None komentar_details["created"] = komentar.created.strftime( TIME_FORMAT) return JsonResponse(return_json, status=HTTPStatus.OK) diff --git a/registrasi/serializers.py b/registrasi/serializers.py index 73bb95e..792ad23 100644 --- a/registrasi/serializers.py +++ b/registrasi/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from colour import Color from django.contrib.auth.models import User from django.utils.regex_helper import _lazy_re_compile @@ -15,13 +16,17 @@ class BisaGoUserSerializer(serializers.ModelSerializer): input_formats=['%d %B %Y', '%Y-%m-%d']) hidden_fields = serializers.SerializerMethodField() + hidden_fields_verbose = serializers.SerializerMethodField() + can_see_hidden_fields = serializers.SerializerMethodField() + hidden_fields_color = serializers.SerializerMethodField() class Meta: model = BisaGoUser fields = ( 'phone_number', 'tanggal_lahir', 'jenis_kelamin', 'name', 'email', 'disabilitas', 'pekerjaan', 'alamat', 'foto', 'seen', 'username', - 'organisasi_komunitas', 'hidden_fields', + 'organisasi_komunitas', 'hidden_fields', 'hidden_fields_verbose', + 'can_see_hidden_fields', 'hidden_fields_color' ) hidden_fields = ('phone_number', 'email', 'alamat', 'tanggal_lahir') @@ -44,18 +49,27 @@ class BisaGoUserSerializer(serializers.ModelSerializer): update_user_fields = ('last_name',) update_user_fields_mapper = {} - def get_hidden_fields(self, *args): + def get_hidden_fields(self, *_): + return self.Meta.hidden_fields + + def get_hidden_fields_verbose(self, *_): hidden_fields = self.Meta.hidden_fields verbose = self.Meta.hidden_fields_verbose return [verbose.get(field) or field for field in hidden_fields] - def can_see_hidden_fields(self, instance): + def get_can_see_hidden_fields(self, instance): + return self._can_see_hidden_fields(instance) + + def get_hidden_fields_color(self, *_): + return Color('#808080').hex_l + + def _can_see_hidden_fields(self, instance): request = self.context['request'] return request.user.id == instance.user.id or instance.seen def to_representation(self, instance): representation = super().to_representation(instance) - if not self.can_see_hidden_fields(instance): + if not self._can_see_hidden_fields(instance): for hidden_field in self.Meta.hidden_fields: verbose_name = self.Meta.hidden_fields_verbose.get(hidden_field)\ or hidden_field diff --git a/requirements.txt b/requirements.txt index e50ab4a..e2767d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -74,3 +74,4 @@ pyfcm==1.4.9 django-mock-queries==2.1.6 mock==4.0.3 model-bakery==1.1.1 +colour==0.1.5 -- GitLab From dd8250ac71fc302a1c7ecb57aa13796a6e771c46 Mon Sep 17 00:00:00 2001 From: Christopher Samuel Date: Fri, 4 Jun 2021 19:58:19 +0700 Subject: [PATCH 46/48] Pbi 14 agenda kegiatan --- informasi_fasilitas/views_kegiatan.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/informasi_fasilitas/views_kegiatan.py b/informasi_fasilitas/views_kegiatan.py index 86c6d04..559ebd2 100644 --- a/informasi_fasilitas/views_kegiatan.py +++ b/informasi_fasilitas/views_kegiatan.py @@ -157,8 +157,7 @@ def search_kegiatan(request, query): @permission_classes([]) def list_kegiatan_by_name(request, query=None): time_now = timezone.now() - queryset = _filter_kegiatan(query) \ - .filter(time_end__gte=time_now).order_by('nama_kegiatan') + queryset = _filter_kegiatan(query).order_by('nama_kegiatan') if not queryset.exists(): raise NotFound(detail=EMPTY_KEGIATAN) serializer = KegiatanSerializerRead(queryset, many=True) @@ -170,8 +169,7 @@ def list_kegiatan_by_name(request, query=None): @permission_classes([]) def list_kegiatan_by_time(request, query=None): time_now = timezone.now() - queryset = _filter_kegiatan(query) \ - .filter(time_end__gte=time_now).order_by('time_start') + queryset = _filter_kegiatan(query).order_by('time_start') if not queryset.exists(): raise NotFound(detail=EMPTY_KEGIATAN) serializer = KegiatanSerializerRead(queryset, many=True) @@ -183,8 +181,7 @@ def list_kegiatan_by_time(request, query=None): @permission_classes([]) def list_kegiatan_by_latest_added(request, query=None): time_now = timezone.now() - queryset = _filter_kegiatan(query) \ - .filter(time_end__gte=time_now).order_by('-id') + queryset = _filter_kegiatan(query).order_by('-id') if not queryset.exists(): raise NotFound(detail=EMPTY_KEGIATAN) serializer = KegiatanSerializerRead(queryset, many=True) -- GitLab From 8d17bef36e062cbd4d5cc5704c0102453914b043 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Sun, 6 Jun 2021 14:07:25 +0700 Subject: [PATCH 47/48] [CHORES] now delete duplicate fcm devices if any except itself after post new fcm device --- notification/permissions.py | 7 --- notification/serializers.py | 29 ++++++++++++ notification/test_permissions.py | 67 --------------------------- notification/test_views.py | 78 +++++++++++++++++++++++++------- notification/views.py | 40 ++-------------- 5 files changed, 95 insertions(+), 126 deletions(-) delete mode 100644 notification/permissions.py create mode 100644 notification/serializers.py delete mode 100644 notification/test_permissions.py diff --git a/notification/permissions.py b/notification/permissions.py deleted file mode 100644 index 566e473..0000000 --- a/notification/permissions.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import permissions - -class NotificationPermission(permissions.BasePermission): - def has_permission(self, request, view): - if view.action in ['list', 'create']: - return request.user.is_authenticated - return False diff --git a/notification/serializers.py b/notification/serializers.py new file mode 100644 index 0000000..fb2a80e --- /dev/null +++ b/notification/serializers.py @@ -0,0 +1,29 @@ +from fcm_django.api.rest_framework import FCMDeviceSerializer + + +class NotificationSerializer(FCMDeviceSerializer): + def to_internal_value(self, data): + request = self.context['request'] + data = request.data + fcm_token = data.get('token') + device_type = data.get('type') + parsed_data={ + 'name': request.user.email, + 'active': request.user.is_active, + 'device_id': '%s - %s' % (request.user.email, + fcm_token[:48] if fcm_token else ''), + 'registration_id': fcm_token, + 'type': device_type, + } + return super().to_internal_value(parsed_data) + + def save(self, **kwargs): + instance = super().save(**kwargs) + ModelClass = self.Meta.model + request = self.context['request'] + ModelClass.objects.filter( + registration_id=instance.registration_id, + user=request.user, + active=False, + ).exclude(pk=instance.pk).delete() + return instance diff --git a/notification/test_permissions.py b/notification/test_permissions.py deleted file mode 100644 index 29ff48d..0000000 --- a/notification/test_permissions.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.test import TestCase -from .permissions import NotificationPermission - -class MockUserAuthenticated: - is_authenticated = True - -class MockUserNotAuthenticated: - is_authenticated = False - -class MockRequest: - def __init__(self, user): - self.user = user - -class MockView: - def __init__(self, action): - self.action = action - -class TestPermission(TestCase): - def setUp(self): - self.notification_permission = NotificationPermission() - self.authenticated_request = MockRequest(MockUserAuthenticated()) - self.not_authenticated_request = MockRequest(MockUserNotAuthenticated()) - - self.view_list = MockView('list') - self.view_create = MockView('create') - self.view_update = MockView('update') - self.view_delete = MockView('delete') - - def test_authenticated_can_list(self): - ret = self.notification_permission\ - .has_permission(self.authenticated_request, self.view_list) - self.assertTrue(ret) - - def test_not_authenticated_cant_list(self): - ret = self.notification_permission\ - .has_permission(self.not_authenticated_request, self.view_list) - self.assertFalse(ret) - - def test_authenticated_can_create(self): - ret = self.notification_permission\ - .has_permission(self.authenticated_request, self.view_create) - self.assertTrue(ret) - - def test_not_authenticated_cant_create(self): - ret = self.notification_permission\ - .has_permission(self.not_authenticated_request, self.view_create) - self.assertFalse(ret) - - def test_authenticated_cant_update(self): - ret = self.notification_permission\ - .has_permission(self.authenticated_request, self.view_update) - self.assertFalse(ret) - - def test_not_authenticated_cant_update(self): - ret = self.notification_permission\ - .has_permission(self.not_authenticated_request, self.view_update) - self.assertFalse(ret) - - def test_authenticated_cant_delete(self): - ret = self.notification_permission\ - .has_permission(self.authenticated_request, self.view_delete) - self.assertFalse(ret) - - def test_not_authenticated_cant_delete(self): - ret = self.notification_permission\ - .has_permission(self.not_authenticated_request, self.view_delete) - self.assertFalse(ret) diff --git a/notification/test_views.py b/notification/test_views.py index 085e6c4..a8dc005 100644 --- a/notification/test_views.py +++ b/notification/test_views.py @@ -3,7 +3,7 @@ from unittest.mock import patch from django.urls import reverse from django.contrib.auth.models import User -from fcm_django.models import FCMDevice, AbstractFCMDevice +from fcm_django.models import FCMDevice from fcm_django.api.rest_framework import FCMDeviceSerializer from informasi_fasilitas.models import Lokasi, Kegiatan, Fasilitas, KomentarFasilitas, KomentarKegiatan @@ -88,22 +88,28 @@ class TestViewsNotification(BaseTestNotification): expected = serializer.data data = response_decode(resp) - self.assertEqual(resp.status_code, HTTPStatus.OK) + self.assertEqual(resp.status_code, HTTPStatus.CREATED) self.assertEqual(data, expected) def test_can_register_device_type_web_or_android_or_ios(self): FCMDevice.objects.all().delete() - device_types = list(map(lambda x: x[0], AbstractFCMDevice.DEVICE_TYPES)) - responses = [] - for _type in device_types: - resp = self.client.post(self.register_notification_url, data={ - 'token': 'simple token', - 'type': _type, - }) - responses.append(resp.status_code) + resp1 = self.client.post(self.register_notification_url, data={ + 'token': 'simple token 1', + 'type': 'android', + }) + resp2 = self.client.post(self.register_notification_url, data={ + 'token': 'simple token 2', + 'type': 'ios', + }) + resp3 = self.client.post(self.register_notification_url, data={ + 'token': 'simple token 3', + 'type': 'web', + }) - self.assertEqual(responses, [HTTPStatus.OK] * len(device_types)) + self.assertEqual( + list(map(lambda resp: resp.status_code, [resp1, resp2, resp3])), + [HTTPStatus.CREATED]*3) def test_cant_register_device_if_device_type_is_not_valid(self): FCMDevice.objects.all().delete() @@ -118,7 +124,7 @@ class TestViewsNotification(BaseTestNotification): self.assertEqual(data, expected) - def test_register_device_return_existing_device_if_duplicate(self): + def test_register_device_duplicate_token_error(self): FCMDevice.objects.all().delete() self.client.post(self.register_notification_url, data={ 'token': 'a', @@ -129,13 +135,10 @@ class TestViewsNotification(BaseTestNotification): 'type': 'android', }) - obj = FCMDevice.objects.filter(registration_id='a', active=True).first() - serializer = FCMDeviceSerializer(obj) - - expected = serializer.data data = response_decode(resp) + expected = {'registration_id': ['This field must be unique.']} - self.assertEqual(resp.status_code, HTTPStatus.OK) + self.assertEqual(resp.status_code, HTTPStatus.BAD_REQUEST) self.assertEqual(data, expected) def test_cant_register_device_if_registration_id_is_null(self): @@ -172,6 +175,47 @@ class TestViewsNotification(BaseTestNotification): self.assertEqual(data, expected) + def test_set_to_inactive_if_change_account(self): + FCMDevice.objects.all().delete() + user2 = User.objects.create_user(username='another dummy') + client2 = get_client_login_with_user(user2) + data = {'token': 'simple token', 'type': 'android'} + self.client.post(self.register_notification_url, data=data) + resp1 = client2.post(self.register_notification_url, data=data) + resp2 = self.client.post(self.register_notification_url, data=data) + + data1 = response_decode(resp1) + data2 = response_decode(resp2) + + devices = FCMDevice.objects.all() + device1 = devices.get(id=data1['id']) + device2 = devices.get(id=data2['id']) + + self.assertFalse(device1.active) + self.assertTrue(device2.active) + + def test_delete_inactive_device_if_change_account(self): + FCMDevice.objects.all().delete() + user2 = User.objects.create_user(username='another dummy') + client2 = get_client_login_with_user(user2) + data = {'token': 'simple token', 'type': 'android'} + resp1 = self.client.post(self.register_notification_url, data=data) + resp2 = client2.post(self.register_notification_url, data=data) + resp3 = self.client.post(self.register_notification_url, data=data) + + data1 = response_decode(resp1) + data2 = response_decode(resp2) + data3 = response_decode(resp3) + + devices = FCMDevice.objects.all() + device1 = devices.filter(id=data1['id']) + device2 = devices.get(id=data2['id']) + device3 = devices.get(id=data3['id']) + + self.assertFalse(device2.active) + self.assertTrue(device3.active) + self.assertFalse(device1.exists()) + @patch('informasi_fasilitas.views.send_komentar_notification') def test_post_komentar_should_call_send_komentar_notification(self, mock_notif): resp = self.komentar_client_sender.post(self.add_komentar_url, data={ diff --git a/notification/views.py b/notification/views.py index 34fe60a..67ccfbd 100644 --- a/notification/views.py +++ b/notification/views.py @@ -1,39 +1,9 @@ -from http import HTTPStatus - from rest_framework.authentication import TokenAuthentication -from rest_framework.response import Response -from rest_framework import viewsets -from fcm_django.api.rest_framework import FCMDeviceViewSet - -from .permissions import NotificationPermission - - -class BaseNotificationViews(viewsets.ModelViewSet): - authentication_classes = [TokenAuthentication] +from fcm_django.api.rest_framework import FCMDeviceAuthorizedViewSet -class NotificationViews(BaseNotificationViews, FCMDeviceViewSet): - permission_classes = (NotificationPermission,) +from .serializers import NotificationSerializer - def get_queryset(self): - return self.queryset.filter(user=self.request.user) - def create(self, request): - data = request.data - fcm_token = data.get('token') - device_type = data.get('type') - existing_fcm_device = self.get_queryset().filter(registration_id=fcm_token) - if existing_fcm_device.exists(): - serializer = self.get_serializer(existing_fcm_device.first()) - return Response(serializer.data, status=HTTPStatus.OK) - serializer = self.get_serializer(data={ - 'name': request.user.last_name, - 'active': request.user.is_active, - 'device_id': '%s - %s' % (request.user.last_name, - fcm_token[:48] if fcm_token else ''), - 'registration_id': fcm_token, - 'type': device_type, - }) - if serializer.is_valid(): - serializer.save(user=request.user) - return Response(serializer.data, status=HTTPStatus.OK) - return Response(serializer.errors, status=HTTPStatus.BAD_REQUEST) +class NotificationViews(FCMDeviceAuthorizedViewSet): + authentication_classes = (TokenAuthentication,) + serializer_class = NotificationSerializer -- GitLab From 8bd88964d9869304a814d5dff4daf614ce45b1b4 Mon Sep 17 00:00:00 2001 From: ariqbasyar Date: Mon, 7 Jun 2021 08:56:46 +0700 Subject: [PATCH 48/48] [CHORE] fix cant add fasilitas --- informasi_fasilitas/serializers.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/informasi_fasilitas/serializers.py b/informasi_fasilitas/serializers.py index ea70f7c..8bcc438 100644 --- a/informasi_fasilitas/serializers.py +++ b/informasi_fasilitas/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers from .models import Lokasi, Fasilitas, Kegiatan, \ - KomentarKegiatan, FotoKegiatan, Likes, Dislikes + KomentarKegiatan, FotoKegiatan, Likes, Dislikes, JENIS_DISABILITAS import pytz TIMEZONE_INDONESIA = { @@ -43,6 +43,9 @@ class FasilitasSerializer(serializers.ModelSerializer): class FasilitasSerializerWrite(FasilitasSerializer): + disabilitas = serializers.MultipleChoiceField(required=True, + choices=JENIS_DISABILITAS) + class Meta: model = FasilitasSerializer.Meta.model fields = FasilitasSerializer.Meta.fields + ('lokasi', 'user') @@ -56,6 +59,13 @@ class FasilitasSerializerWrite(FasilitasSerializer): 'jumlah': {"required": True}, } + def to_internal_value(self, data): + parsed_data = data.copy() + input_disabilitas = parsed_data.get('disabilitas') + if input_disabilitas is not None: + parsed_data['disabilitas'] = input_disabilitas.split() + return super().to_internal_value(parsed_data) + class FasilitasSerializerRead(FasilitasSerializer): place_id = serializers.CharField(source='lokasi.place_id', read_only=True) -- GitLab