Fakultas Ilmu Komputer UI

Commit fc1bdc07 authored by Zafir Rasyidi Taufik's avatar Zafir Rasyidi Taufik
Browse files

Merge branch 'zafir/csv-xls-converter' into 'pbi-10-tabel-statistik-kasus'

Zafir/csv xls converter

See merge request !26
parents 14bbcc67 b54e1101
Pipeline #77086 passed with stages
in 11 minutes
......@@ -3,6 +3,7 @@ stages:
- sonar-scanner
- publish
- deploy
- performance-test
.cd-switch-environment: &cd-switch-environment
# select environment based on branch name
......@@ -20,12 +21,16 @@ stages:
refs:
- /^[Pp][Bb][Ii]-[0-9]+-.*$/
- staging
except:
- schedules
before_script:
- *cd-switch-environment
test:
image: python:3.6.5
stage: test
except:
- schedules
before_script:
- pip install -r requirements.txt
- python manage.py migrate
......@@ -48,6 +53,8 @@ sonar-scanner:
name: sonarsource/sonar-scanner-cli
entrypoint: [""]
stage: sonar-scanner
except:
- schedules
script:
- sonar-scanner
-Dsonar.host.url=$SONARQUBE_URL
......@@ -72,6 +79,8 @@ publish:
deploy:
extends: .cd-job-template
stage: deploy
except:
- schedules
before_script:
- *cd-switch-environment
- eval $(ssh-agent -s)
......@@ -89,3 +98,24 @@ deploy:
docker-compose up -d &&
exit
"
load-test:
image: python:3.6.5
stage: performance-test
only:
- staging
- schedules
allow_failure: true
before_script:
- cd locust
- pip install -r requirements.txt
script:
- locust -f locust.py --headless -u 100 -r 10 -t 10m -H https://tbcare-be-staging.cs.ui.ac.id --csv=load_test
after_script:
- cd locust
- ls -al
- tar czvf load_test_result.tar.gz *.csv
artifacts:
paths:
- locust/*.csv
- locust/load_test_result.tar.gz
\ No newline at end of file
FROM python:3.9.3-alpine3.13
FROM python:3.9.5-slim
WORKDIR /srv
......@@ -6,22 +6,8 @@ ENV PYTHONUNBUFFERED 1
COPY requirements.txt .
RUN apk add --no-cache --virtual .build-deps \
gcc postgresql-dev libpq musl-dev jpeg-dev zlib-dev libffi-dev \
&& pip install -r requirements.txt \
&& find /usr/local \
\( -type d -a -name test -o -name tests \) \
-o \( -type f -a -name '*.pyc' -o -name '*.pyo' \) \
-exec rm -rf '{}' + \
&& runDeps="$( \
scanelf --needed --nobanner --recursive /usr/local \
| awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
| sort -u \
| xargs -r apk info --installed \
| sort -u \
)" \
&& apk add --virtual .rundeps $runDeps \
&& apk del .build-deps
RUN apt-get -y update && apt-get -y install libpq-dev gcc
RUN pip install -r requirements.txt
# copy required files to run
COPY project project
......
# Generated by Django 3.0.1 on 2021-05-16 12:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_auto_20210404_2332'),
]
operations = [
migrations.AddField(
model_name='accounthistory',
name='last_modified_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name='account',
name='sub_district',
field=models.CharField(choices=[('Beji', 'Beji'), ('Beji Timur', 'Beji Timur'), ('Kemirimuka', 'Kemirimuka'), ('Kukusan', 'Kukusan'), ('Pondok Cina', 'Pondok Cina'), ('Tanah Baru', 'Tanah Baru'), ('Bojongsari Baru', 'Bojongsari Baru'), ('Bojongsari Lama', 'Bojongsari Lama'), ('Curug', 'Curug'), ('Duren Mekar', 'Duren Mekar'), ('Duren Seribu', 'Duren Seribu'), ('Pondok Petir', 'Pondok Petir'), ('Serua', 'Serua'), ('Cilodong', 'Cilodong'), ('Jatimulya', 'Jatimulya'), ('Kalibaru', 'Kalibaru'), ('Kalimulya', 'Kalimulya'), ('Sukamaju', 'Sukamaju'), ('Cisalak Pasar', 'Cisalak Pasar'), ('Curug', 'Curug'), ('Harjamukti', 'Harjamukti'), ('Mekarsari', 'Mekarsari'), ('Pasir Gunung Selatan', 'Pasir Gunung Selatan'), ('Tugu', 'Tugu'), ('Cinere', 'Cinere'), ('Gandul', 'Gandul'), ('Pangkalan Jati', 'Pangkalan Jati'), ('Pangkalan Jati Baru', 'Pangkalan Jati Baru'), ('Bojong Pondok Terong', 'Bojong Pondok Terong'), ('Cipayung', 'Cipayung'), ('Cipayung Jaya', 'Cipayung Jaya'), ('Pondok Jaya', 'Pondok Jaya'), ('Ratujaya', 'Ratujaya'), ('Grogol', 'Grogol'), ('Krukut', 'Krukut'), ('Limo', 'Limo'), ('Meruyung', 'Meruyung'), ('Depok Jaya', 'Depok'), ('Depok Jaya', 'Depok Jaya'), ('Mampang', 'Mampang'), ('Pancoran Mas', 'Pancoran Mas'), ('Rangkapan Jaya', 'Rangkapan Jaya'), ('Rangkapan Jaya Baru', 'Rangkapan Jaya Baru'), ('Bedahan', 'Bedahan'), ('Cinangka', 'Cinangka'), ('Kedaung', 'Kedaung'), ('Pasir Putih', 'Pasir Putih'), ('Pengasinan', 'Pengasinan'), ('Sawangan Baru', 'Sawangan Baru'), ('Sawangan Lama', 'Sawangan Lama'), ('Abadijaya', 'Abadijaya'), ('Bakti Jaya', 'Bakti Jaya'), ('Cisalak', 'Cisalak'), ('Mekar Jaya', 'Mekar Jaya'), ('Sukmajaya', 'Sukmajaya'), ('Tirtajaya', 'Tirtajaya'), ('Cilangkap', 'Cilangkap'), ('Cimpaeun', 'Cimpaeun'), ('Jatijajar', 'Jatijajar'), ('Leuwinanggung', 'Leuwinanggung'), ('Sukamaju Baru', 'Sukamaju Baru'), ('Sukatani', 'Sukatani'), ('Tapos', 'Tapos')], max_length=128),
),
migrations.AlterField(
model_name='accounthistory',
name='sub_district',
field=models.CharField(choices=[('Beji', 'Beji'), ('Beji Timur', 'Beji Timur'), ('Kemirimuka', 'Kemirimuka'), ('Kukusan', 'Kukusan'), ('Pondok Cina', 'Pondok Cina'), ('Tanah Baru', 'Tanah Baru'), ('Bojongsari Baru', 'Bojongsari Baru'), ('Bojongsari Lama', 'Bojongsari Lama'), ('Curug', 'Curug'), ('Duren Mekar', 'Duren Mekar'), ('Duren Seribu', 'Duren Seribu'), ('Pondok Petir', 'Pondok Petir'), ('Serua', 'Serua'), ('Cilodong', 'Cilodong'), ('Jatimulya', 'Jatimulya'), ('Kalibaru', 'Kalibaru'), ('Kalimulya', 'Kalimulya'), ('Sukamaju', 'Sukamaju'), ('Cisalak Pasar', 'Cisalak Pasar'), ('Curug', 'Curug'), ('Harjamukti', 'Harjamukti'), ('Mekarsari', 'Mekarsari'), ('Pasir Gunung Selatan', 'Pasir Gunung Selatan'), ('Tugu', 'Tugu'), ('Cinere', 'Cinere'), ('Gandul', 'Gandul'), ('Pangkalan Jati', 'Pangkalan Jati'), ('Pangkalan Jati Baru', 'Pangkalan Jati Baru'), ('Bojong Pondok Terong', 'Bojong Pondok Terong'), ('Cipayung', 'Cipayung'), ('Cipayung Jaya', 'Cipayung Jaya'), ('Pondok Jaya', 'Pondok Jaya'), ('Ratujaya', 'Ratujaya'), ('Grogol', 'Grogol'), ('Krukut', 'Krukut'), ('Limo', 'Limo'), ('Meruyung', 'Meruyung'), ('Depok Jaya', 'Depok'), ('Depok Jaya', 'Depok Jaya'), ('Mampang', 'Mampang'), ('Pancoran Mas', 'Pancoran Mas'), ('Rangkapan Jaya', 'Rangkapan Jaya'), ('Rangkapan Jaya Baru', 'Rangkapan Jaya Baru'), ('Bedahan', 'Bedahan'), ('Cinangka', 'Cinangka'), ('Kedaung', 'Kedaung'), ('Pasir Putih', 'Pasir Putih'), ('Pengasinan', 'Pengasinan'), ('Sawangan Baru', 'Sawangan Baru'), ('Sawangan Lama', 'Sawangan Lama'), ('Abadijaya', 'Abadijaya'), ('Bakti Jaya', 'Bakti Jaya'), ('Cisalak', 'Cisalak'), ('Mekar Jaya', 'Mekar Jaya'), ('Sukmajaya', 'Sukmajaya'), ('Tirtajaya', 'Tirtajaya'), ('Cilangkap', 'Cilangkap'), ('Cimpaeun', 'Cimpaeun'), ('Jatijajar', 'Jatijajar'), ('Leuwinanggung', 'Leuwinanggung'), ('Sukamaju Baru', 'Sukamaju Baru'), ('Sukatani', 'Sukatani'), ('Tapos', 'Tapos')], max_length=128),
),
]
# Generated by Django 3.0.1 on 2021-05-16 12:20
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_auto_20210516_1918'),
]
operations = [
migrations.RemoveField(
model_name='accounthistory',
name='last_modified_at',
),
]
......@@ -38,6 +38,7 @@ class AccountHistory(HistoryModel):
raise ValidationError(_('Inconsistent district and sub district value'))
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
......@@ -86,4 +87,5 @@ class Account(HistoryEnabledModel):
raise ValidationError(_('Inconsistent district and sub district value'))
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
......@@ -3,9 +3,11 @@ import random
from faker import Faker
from django.urls import reverse
from django.core import mail
from django.core.exceptions import ValidationError
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase, APIClient
from unittest.mock import patch
from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory
from apps.accounts.models import Account, AccountHistory
......@@ -206,6 +208,28 @@ class AccountViewTest(APITestCase):
response = self.client.post(path=url, data=data, format="json",)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@patch('apps.accounts.models.Account.objects.create')
def test_create_new_account_fail_should_roll_back_user_creation(self, account_mock):
url = self.BASE_URL
_account_id = self.faker.email()
data = {
"name": self.officer.name,
"username": _account_id,
"password": "justpass",
"email": _account_id,
"phone_number": "+999999999999",
"district": self.officer.district,
"sub_district": self.officer.sub_district,
"is_admin": False,
"is_verified": False,
"is_active": False,
}
account_mock.side_effect = ValidationError('ValidationError raised!')
response = self.client.post(path=url, data=data, format="json",)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_create_new_account_fails_with_invalid_district_value(self):
url = self.BASE_URL
_account_id = self.faker.email()
......
from django.test.testcases import TestCase
from django.core.exceptions import ValidationError
from ...models import Account, AccountHistory
class AccountModelTest(TestCase):
def test_validate_inconsistent_district_data_should_raise_error(self):
account = Account(district='Beji', sub_district='Limo')
self.assertRaises(ValidationError, account.save)
class AccountHistoryModelTest(TestCase):
def test_validate_inconsistent_district_data_should_raise_error(self):
account = AccountHistory(district='Beji', sub_district='Limo')
self.assertRaises(ValidationError, account.save)
\ No newline at end of file
from django.test.testcases import TestCase
from rest_framework.exceptions import ValidationError
from ...serializers import AccountSerializer, AccountRegisterSerializer
from ..factories.accounts import AccountFactory
class AccountSerializerTest(TestCase):
def test_account_serializer_inconsistent_district_mapping_should_raise_error(
self):
account = AccountFactory()
data = {
'id': account.id,
'name': account.name,
'district': 'Beji',
'sub_district': 'Limo',
'username': account.user.username,
'email': account.email,
'phone_number': account.phone_number,
'is_admin': account.is_admin,
'is_verified': account.is_verified,
'is_active': account.is_active,
}
self.assertRaises(
ValidationError,
AccountSerializer(
data=data).is_valid,
raise_exception=True)
class AccountRegisterSerializerTest(TestCase):
def test_account_register_serializer_inconsistent_district_mapping_should_raise_error(
self):
account = AccountFactory()
data = {
'id': account.id,
'name': account.name,
'district': 'Beji',
'sub_district': 'Limo',
'username': account.user.username,
'password': 'asdfasdfasdf',
'email': account.email,
'phone_number': account.phone_number,
'is_admin': account.is_admin,
'is_verified': account.is_verified,
'is_active': account.is_active,
}
self.assertRaises(
ValidationError,
AccountRegisterSerializer(
data=data).is_valid,
raise_exception=True)
......@@ -46,6 +46,7 @@ class CaseSubjectHistory(HistoryModel):
raise ValidationError(_('Inconsistent district and sub district value'))
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
......@@ -83,6 +84,7 @@ class CaseSubject(HistoryEnabledModel):
raise ValidationError(_('Inconsistent district and sub district value'))
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
......
......@@ -40,7 +40,6 @@ class CaseSubjectSerializer(serializers.ModelSerializer):
class CaseSubjectHistorySerializer(serializers.ModelSerializer):
class Meta:
model = CaseSubjectHistory
fields = [
......
import uuid
from apps.cases.models import CaseSubject
from apps.cases.models import CaseSubject, CaseSubjectHistory
from apps.cases.constants import (
DISTRICT_CHOICES,
SUB_DISTRICT_MAPPING
......@@ -24,3 +24,15 @@ class CaseSubjectFactory(factory.DjangoModelFactory):
address = faker.address()
district = random.choice(DISTRICT_CHOICES)[0]
sub_district = factory.LazyAttribute(lambda o: random.choice(SUB_DISTRICT_MAPPING[o.district]))
class CaseSubjectHistoryFactory(factory.DjangoModelFactory):
class Meta:
model = CaseSubjectHistory
name = faker.name()
age = 19
is_male = True
address = faker.address()
district = random.choice(DISTRICT_CHOICES)[0]
sub_district = factory.LazyAttribute(lambda o: random.choice(SUB_DISTRICT_MAPPING[o.district]))
from django.test.testcases import TestCase
from django.core.exceptions import ValidationError
from ...models import CaseSubject, CaseSubjectHistory
class CaseSubjectModelTest(TestCase):
def test_validate_inconsistent_district_data_should_raise_error(self):
case_subject = CaseSubject(district='Beji', sub_district='Limo')
self.assertRaises(ValidationError, case_subject.save)
class CaseSubjectHistoryModelTest(TestCase):
def test_validate_inconsistent_district_data_should_raise_error(self):
case_subject = CaseSubjectHistory(district='Beji', sub_district='Limo')
self.assertRaises(ValidationError, case_subject.save)
\ No newline at end of file
from django.test.testcases import TestCase
from rest_framework.exceptions import ValidationError
from ...serializers import CaseSubjectSerializer, CaseSubjectHistorySerializer
from ..factories.case_subjects import CaseSubjectFactory, CaseSubjectHistoryFactory
class CaseSubjectSerializerTest(TestCase):
def test_case_subject_serializer_inconsistent_district_mapping_should_raise_error(
self):
case_subject = CaseSubjectFactory()
data = {
'id': case_subject.id,
'name': case_subject.name,
'age': case_subject.age,
'is_male': case_subject.is_male,
'address': case_subject.address,
'district': 'Beji',
'sub_district': 'Limo',
'created_at': case_subject.created_at,
}
self.assertRaises(
ValidationError,
CaseSubjectSerializer(
data=data).is_valid,
raise_exception=True)
class CaseSubjectHistorySerializerTest(TestCase):
def test_validate_should_return_data(self):
case_subject_history = CaseSubjectHistoryFactory(action_type='Create')
data = {
"revision_id": case_subject_history.revision_id,
"object_id": case_subject_history.object_id,
"name": case_subject_history.name,
"age": case_subject_history.age,
"is_male": case_subject_history.is_male,
"address": case_subject_history.address,
"district": case_subject_history.district,
"sub_district": case_subject_history.sub_district,
"action_type": 'Create',
"recorded_at": case_subject_history.recorded_at,
}
serializer = CaseSubjectHistorySerializer(data=data)
serializer.is_valid()
def test_validate_inconsistent_district_mapping_should_raise_error(
self):
case_subject_history = CaseSubjectHistoryFactory()
data = {
"revision_id": case_subject_history.revision_id,
"object_id": case_subject_history.object_id,
"name": case_subject_history.name,
"age": case_subject_history.age,
"is_male": case_subject_history.is_male,
"address": case_subject_history.address,
"district": 'Beji',
"sub_district": 'Limo',
"action_type": "Create",
"recorded_at": case_subject_history.recorded_at,
}
self.assertRaises(
ValidationError,
CaseSubjectHistorySerializer(
data=data).is_valid,
raise_exception=True)
from django.http import request
from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
......@@ -14,6 +15,8 @@ class ExportInvestigationCaseQuerySerializer(serializers.Serializer):
district = serializers.CharField(required=False)
sub_district = serializers.CharField(required=False)
download_as_xls = serializers.NullBooleanField(required=False)
start_date = serializers.DateField(required=False)
end_date = serializers.DateField(required=False)
def validate(self, data):
data = super().validate(data)
......@@ -25,12 +28,25 @@ class ExportInvestigationCaseQuerySerializer(serializers.Serializer):
class CaseCountsQuerySerializer(serializers.Serializer):
is_male = serializers.NullBooleanField(required=False)
min_age = serializers.IntegerField(required=False, min_value=0)
max_age = serializers.IntegerField(required=False, min_value=0)
district = serializers.CharField(required=False)
sub_district = serializers.CharField(required=False)
download_as_xls = serializers.NullBooleanField(required=False)
start_date = serializers.DateField(default=datetime(1, 1, 2).date())
end_date = serializers.DateField(default=datetime(9999, 12, 30).date())
def validate(self, data):
data['start_date'] = get_datetime_from_iso_format(
str(data['start_date']), pytz.timezone(settings.TIME_ZONE), 'start')
data['end_date'] = get_datetime_from_iso_format(
str(data['end_date']), pytz.timezone(settings.TIME_ZONE), 'end')
try:
data['start_date'] = get_datetime_from_iso_format(
str(data['start_date']), pytz.timezone(settings.TIME_ZONE), 'start')
data['end_date'] = get_datetime_from_iso_format(
str(data['end_date']), pytz.timezone(settings.TIME_ZONE), 'end')
except ValueError as e:
raise ValidationError(_("Date format invalid, must be using iso format 'YYYY-MM-DD'."))
if data['end_date'] < data['start_date']:
raise ValidationError(_("Invalid date range."))
return data
......@@ -85,6 +85,10 @@ class ExportInvestigationCaseView(APIView):
query.is_valid(raise_exception=True)
query = query.validated_data
query = CaseCountsQuerySerializer(data=query)
query.is_valid(raise_exception=True)
query = query.validated_data
cases = AllCaseFilter()
if 'is_male' in query:
cases = SexFilterDecorator(cases, query['is_male'])
......@@ -94,6 +98,8 @@ class ExportInvestigationCaseView(APIView):
cases = DistrictFilterDecorator(cases, query['district'])
if 'sub_district' in query:
cases = SubDistrictFilterDecorator(cases, query['sub_district'])
if 'start_date' in query and 'end_date' in query:
cases = CreatedDateFilterDecorator(cases, start_date=query['start_date'], end_date=query['end_date'])
data = list(
cases.filter().prefetch_related(
......
BEJI = "Beji"
BEJI_TIMUR = "Beji Timur"
KEMIRIMUKA = "Kemirimuka"
KUKUSAN = "Kukusan"
PONDOK_CINA = "Pondok Cina"
TANAH_BARU = "Tanah Baru"
BOJONGSARI = "Bojongsari"
BOJONGSARI_BARU = "Bojongsari Baru"
BOJONGSARI_LAMA = "Bojongsari Lama"
CURUG = "Curug"
DUREN_MEKAR = "Duren Mekar"
DUREN_SERIBU = "Duren Seribu"
PONDOK_PETIR = "Pondok Petir"
SERUA = "Serua"
CILODONG = "Cilodong"
JATIMULYA = "Jatimulya"
KALIBARU = "Kalibaru"
KALIMULYA = "Kalimulya"
SUKAMAJU = "Sukamaju"
CIMANGGIS = "Cimanggis"
CISALAK_PASAR = "Cisalak Pasar"
HARJAMUKTI = "Harjamukti"
MEKARSARI = "Mekarsari"
PASIR_GUNUNG_SELATAN = "Pasir Gunung Selatan"
TUGU = "Tugu"
CINERE = "Cinere"
GANDUL = "Gandul"
PANGKALAN_JATI = "Pangkalan Jati"
PANGKALAN_JATI_BARU = "Pangkalan Jati Baru"
CIPAYUNG = "Cipayung"
BOJONG_PONDOK_TERONG = "Bojong Pondok Terong"
CIPAYUNG_JAYA = "Cipayung Jaya"
PONDOK_JAYA = "Pondok Jaya"
RATUJAYA = "Ratujaya"
LIMO = "Limo"
GROGOL = "Grogol"
KRUKUT = "Krukut"
MERUYUNG = "Meruyung"
PANCORAN_MAS = "Pancoran Mas"
DEPOK = "Depok"
DEPOK_JAYA = "Depok Jaya"
MAMPANG = "Mampang"
RANGKAPAN_JAYA = "Rangkapan Jaya"
RANGKAPAN_JAYA_BARU = "Rangkapan Jaya Baru"
SAWANGAN = "Sawangan"
BEDAHAN = "Bedahan"
CINANGKA = "Cinangka"
KEDAUNG = "Kedaung"
PASIR_PUTIH = "Pasir Putih"
PENGASINAN = "Pengasinan"
SAWANGAN_BARU = "Sawangan Baru"
SAWANGAN_LAMA = "Sawangan Lama"
SUKMAJAYA = "Sukmajaya"
ABADIJAYA = "Abadijaya"
BAKTI_JAYA = "Bakti Jaya"
CISALAK = "Cisalak"
MEKAR_JAYA = "Mekar Jaya"
TIRTAJAYA = "Tirtajaya"
TAPOS = "Tapos"
CILANGKAP = "Cilangkap"
CIMPAEUN = "Cimpaeun"
JATIJAJAR = "Jatijajar"
LEUWINANGGUNG = "Leuwinanggung"
SUKAMAJU_BARU = "Sukamaju Baru"
SUKATANI = "Sukatani"
DISTRICT_CHOICES = [
(BEJI, "Beji"),
(BOJONGSARI, "Bojongsari"),
(CILODONG, "Cilodong"),
(CIMANGGIS, "Cimanggis"),
(CINERE, "Cinere"),
(CIPAYUNG, "Cipayung"),
(LIMO, "Limo"),
(PANCORAN_MAS, "Pancoran Mas"),
(SAWANGAN, "Sawangan"),
(SUKMAJAYA, "Sukmajaya"),
(TAPOS, "Tapos")
]
SUB_DISTRICT_CHOICES = [
(BEJI, 'Beji'),
(BEJI_TIMUR, 'Beji Timur'),
(KEMIRIMUKA, 'Kemirimuka'),
(KUKUSAN, 'Kukusan'),
(PONDOK_CINA, 'Pondok Cina'),
(TANAH_BARU, 'Tanah Baru'),
(BOJONGSARI_BARU, 'Bojongsari Baru'),
(BOJONGSARI_LAMA, 'Bojongsari Lama'),
(CURUG, 'Curug'),
(DUREN_MEKAR, 'Duren Mekar'),
(DUREN_SERIBU, 'Duren Seribu'),
(PONDOK_PETIR, 'Pondok Petir'),
(SERUA, 'Serua'),
(CILODONG, 'Cilodong'),
(JATIMULYA, 'Jatimulya'),
(KALIBARU, 'Kalibaru'),
(KALIMULYA, 'Kalimulya'),
(SUKAMAJU, 'Sukamaju'),
(CISALAK_PASAR, 'Cisalak Pasar'),
(CURUG, 'Curug'),
(HARJAMUKTI, 'Harjamukti'),
(MEKARSARI, 'Mekarsari'),
(PASIR_GUNUNG_SELATAN, 'Pasir Gunung Selatan'),
(TUGU, 'Tugu'),
(CINERE, 'Cinere'),
(GANDUL, 'Gandul'),
(PANGKALAN_JATI, 'Pangkalan Jati'),
(PANGKALAN_JATI_BARU, 'Pangkalan Jati Baru'),
(BOJONG_PONDOK_TERONG, 'Bojong Pondok Terong'),
(CIPAYUNG, 'Cipayung'),
(CIPAYUNG_JAYA, 'Cipayung Jaya'),
(PONDOK_JAYA, 'Pondok Jaya'),
(RATUJAYA, 'Ratujaya'),
</