diff --git a/Dockerfile b/Dockerfile index 59005e65a70c62b7264a69468a2c0fa93f36c744..2c9d4ff9f78656a026c6a9ada9f2e662347a5035 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ENV PYTHONUNBUFFERED 1 COPY ./requirements.txt . RUN apk add --no-cache --virtual .build-deps \ - gcc postgresql-dev libpq musl-dev \ + gcc postgresql-dev libpq musl-dev jpeg-dev zlib-dev\ && pip install --upgrade pip \ && pip install -r requirements.txt \ && find /usr/local \ diff --git a/docker-compose.yml b/docker-compose.yml index c1527a1ba37f8615efacb1248dc67ed61ebfa98a..e280d565f44bf0cc8ef1b6aa4093a6712c60c5ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - staticfiles:/var/www/html/static/ + - mediafiles:/var/www/html/media/ depends_on: - backend @@ -20,6 +21,7 @@ services: volumes: - ./:/app/backend/ - staticfiles:/app/backend/staticfiles/ + - mediafiles:/app/backend/mediafiles/ env_file: - .env environment: @@ -39,3 +41,4 @@ services: volumes: postgres_data: staticfiles: + mediafiles: diff --git a/nginx/default.conf b/nginx/default.conf index 4b5e6f7824de8d6b87d48144b37cd23fee6fb690..aef744de213ba0998d900d98af5ae84b99323ec4 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -5,6 +5,10 @@ server { root /var/www/html/; } + location /media/ { + root /var/www/html/; + } + location / { proxy_pass http://backend:8000; proxy_set_header Host $http_host; diff --git a/requirements.txt b/requirements.txt index cb089eea7d140094d61e6c9a7f63484a036cadea..fca7eb3fda71c97e24e46ad160e659d6f53ec3c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,6 @@ django-graphql-jwt==0.3.1 graphene-django==2.10.1 gunicorn==20.0.4 psycopg2-binary==2.8.5 +Pillow==7.2.0 pytz==2020.1 sqlparse==0.3.1 diff --git a/sizakat/mustahik/admin.py b/sizakat/mustahik/admin.py index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..bca847d3ffaf15fd0667cad74bf069b52a5dcedb 100644 --- a/sizakat/mustahik/admin.py +++ b/sizakat/mustahik/admin.py @@ -1,3 +1,17 @@ from django.contrib import admin -# Register your models here. +from .models import ( + Mustahik, + DataSource, + DataSourceWarga, + DataSourceInstitusi, + DataSourcePekerja, +) + +admin.site.register([ + Mustahik, + DataSource, + DataSourceWarga, + DataSourceInstitusi, + DataSourcePekerja, +]) diff --git a/sizakat/mustahik/forms.py b/sizakat/mustahik/forms.py index 9af65d63a6c0998df6c1a5cbbfc41a9a6504e78e..d6b1039618500006dc6564cb98f47701cd5beb83 100644 --- a/sizakat/mustahik/forms.py +++ b/sizakat/mustahik/forms.py @@ -55,6 +55,7 @@ class DataSourceInstitusiForm(forms.ModelForm): 'pic_position', 'name', 'province', + 'regency', 'sub_district', 'village', 'rt', diff --git a/sizakat/mustahik/migrations/0002_auto_20200816_1347.py b/sizakat/mustahik/migrations/0002_auto_20200816_1347.py new file mode 100644 index 0000000000000000000000000000000000000000..457c1dfdb18de2d09c3faf91844c818d0b363dd7 --- /dev/null +++ b/sizakat/mustahik/migrations/0002_auto_20200816_1347.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.7 on 2020-08-16 13:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mustahik', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='datasourceinstitusi', + name='address', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='datasourceinstitusi', + name='pic_position', + field=models.CharField(blank=True, max_length=50, null=True), + ), + ] diff --git a/sizakat/mustahik/migrations/0003_change_mustahik_photo_to_imagefield.py b/sizakat/mustahik/migrations/0003_change_mustahik_photo_to_imagefield.py new file mode 100644 index 0000000000000000000000000000000000000000..c5ac8ca905b4b7ff638fc32313c01e21a4a26a32 --- /dev/null +++ b/sizakat/mustahik/migrations/0003_change_mustahik_photo_to_imagefield.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-09-04 11:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mustahik', '0002_auto_20200816_1347'), + ] + + operations = [ + migrations.AlterField( + model_name='mustahik', + name='photo', + field=models.ImageField(default='images/default_photo.jpg', upload_to='images/mustahik'), + ), + ] diff --git a/sizakat/mustahik/models.py b/sizakat/mustahik/models.py index bf217851a23b10f43ddfbb0d59795bd5db415af6..40932b6e05f8a202b0778765e3c81af719f784d5 100644 --- a/sizakat/mustahik/models.py +++ b/sizakat/mustahik/models.py @@ -33,7 +33,7 @@ class Mustahik(models.Model): birthdate = models.DateField() status = models.CharField(max_length=32, choices=Status.choices) gender = models.CharField(max_length=1, choices=Gender.choices) - photo = models.FileField( + photo = models.ImageField( upload_to=os.path.join('images', 'mustahik'), default=os.path.join('images', 'default_photo.jpg') ) @@ -105,11 +105,12 @@ class DataSourceInstitusi(DataSourceDetail): rw = models.CharField( max_length=3, validators=[validate_numeric_character] ) - address = models.CharField(max_length=255) + address = models.CharField(max_length=255, blank=True, null=True) data_source = models.OneToOneField( 'DataSource', on_delete=models.CASCADE, limit_choices_to={'category': DataSource.Category.INSTITUSI} ) + pic_position = models.CharField(max_length=50, blank=True, null=True) class DataSourcePekerja(DataSourceDetail): diff --git a/sizakat/mustahik/query.py b/sizakat/mustahik/query.py index 3e989cec27a18102246bbabaf202f0be878c9b45..72667bc32d51d4d67a1eed925cd956094872978c 100644 --- a/sizakat/mustahik/query.py +++ b/sizakat/mustahik/query.py @@ -11,26 +11,35 @@ class MustahikQuery(graphene.ObjectType): mustahiks = graphene.List( MustahikType, statuses=graphene.List(graphene.String), - name_contains=graphene.String() + name_contains=graphene.String(), + data_sources=graphene.List(graphene.ID) ) mustahik = graphene.Field(MustahikType, id=graphene.ID(required=True)) data_sources = graphene.List( DataSourceType, category=graphene.String(), - name_contains=graphene.String() + name_contains=graphene.String(), + picName_contains=graphene.String() ) data_source = graphene.Field(DataSourceType, id=graphene.ID(required=True)) def resolve_mustahiks(self, info, **kwargs): statuses = kwargs.get('statuses', None) + data_sources = kwargs.get('data_sources', None) name_contains = kwargs.get('name_contains', None) filter_query = Q() if statuses and len(statuses) > 0: - filter_query |= reduce( + filter_query &= reduce( lambda a, b: a | b, [Q(status=status) for status in statuses] ) + if data_sources and len(data_sources) > 0: + filter_query &= reduce( + lambda a, b: a | b, + [Q(data_source=data_source) for data_source in data_sources] + ) + if name_contains: filter_query &= Q(name__icontains=name_contains) @@ -39,9 +48,13 @@ class MustahikQuery(graphene.ObjectType): def resolve_mustahik(self, info, id): return Mustahik.objects.get(pk=id) + def resolve_data_source(self, info, id): + return DataSource.objects.get(pk=id) + def resolve_data_sources(self, info, **kwargs): category = kwargs.get('category', None) query = kwargs.get('name_contains', None) + picName = kwargs.get('picName_contains', None) filter_query = Q() if category: filter_query &= Q(category=category) @@ -56,7 +69,14 @@ class MustahikQuery(graphene.ObjectType): | Q(datasourcewarga__village__icontains=query) ) + if picName: + filter_query &= ( + Q(datasourcepekerja__pic_name__icontains=picName) + | Q(datasourceinstitusi__pic_name__icontains=picName) + | Q(datasourcewarga__pic_name__icontains=picName) + + ) + return DataSource.objects.filter(filter_query) - def resolve_data_source(self, info, id): - return DataSource.objects.get(pk=id) + diff --git a/sizakat/mustahik/tests.py b/sizakat/mustahik/tests.py index 082e828b57810fa527b6b0e41b4e0f8e56b4cad5..f82c5f69f757964a893bf44a7019836191091ffd 100644 --- a/sizakat/mustahik/tests.py +++ b/sizakat/mustahik/tests.py @@ -361,6 +361,44 @@ class MustahikGraphQLTestCase(GraphQLTestCase): content = json.loads(response.content) self.assertEqual(len(content['data']['mustahiks']), 0) + def test_mustahiks_query_if_data_sources_provided_should_return_mustahiks_with_coresponding_data_sources(self): + dataSourceId = DataSource.objects.all()[0].id + response = self.query( + ''' + query mustahiks($dataSources: [ID]) { + mustahiks(dataSources: $dataSources) { + dataSource { + id + } + } + } + ''', + op_name='mustahiks', + variables={'dataSources': [dataSourceId]} + ) + + content = json.loads(response.content) + self.assertEqual(int(content['data']['mustahiks'][0]['dataSource']['id']), dataSourceId) + + def test_mustahiks_query_if_data_sources_provided_has_no_corresponding_mustahiks_it_should_return_empty_list(self): + dataSourceId = 99999 + response = self.query( + ''' + query mustahiks($dataSources: [ID]) { + mustahiks(dataSources: $dataSources) { + dataSource { + id + } + } + } + ''', + op_name='mustahiks', + variables={'dataSources': [dataSourceId]} + ) + + content = json.loads(response.content) + self.assertEqual(len(content['data']['mustahiks']), 0) + def test_data_sources_query_should_return_list_data_sources(self): response = self.query( ''' @@ -479,6 +517,7 @@ class MustahikGraphQLTestCase(GraphQLTestCase): self.assertEqual(content['data']['q3']['id'], str(institusi_detail.data_source.pk)) self.assertEqual(content['data']['q3']['detail']['__typename'], 'DataSourceInstitusiType') + def test_data_source_mutation_can_add_new_data_source(self): existing_data_source_ammount = DataSource.objects.count() response = self.query( @@ -739,6 +778,7 @@ class MustahikGraphQLTestCase(GraphQLTestCase): "picPosition": "Vice", "name": "Pesantren Yatim", "province": "Jawa Barat", + "regency": "Kabupaten", "subDistrict": "Dusun", "village": "desa", "rt": "002", @@ -803,14 +843,13 @@ class MustahikGraphQLTestCase(GraphQLTestCase): "picKtp": pic_ktp, "picName": new_pic_name, "picPhone": "123456789012", - "picPosition": "Head", "name": "Institusi Bandung", "province": "Jawa Barat", + "regency": "Kota", "subDistrict": "Bogor", "village": "Desa", "rt": "001", "rw": "001", - "address": "Jalan suatu desa no 1", "dataSource": data_source_institusi.pk, "id": source_institusi.pk, } @@ -871,3 +910,38 @@ class MustahikGraphQLTestCase(GraphQLTestCase): datasource_warga = datasource['dataSourceDetail'] kelurahan = datasource_warga.get('village', None) self.assertIn(kelurahan, ['pinangranti', None]) + + def test_query_search_by_picname(self): + response = self.query( + ''' + { + dataSources(picNameContains:"pic test") { + id + category + dataSourceDetail { + __typename + ... on DataSourceInstitusiType { + picName + name + } + ... on DataSourcePekerjaType { + picName + profession + location + } + ... on DataSourceWargaType { + picName + rt + rw + village + } + } + } + } + ''') + self.assertResponseNoErrors(response) + datasources = json.loads(response.content)['data']['dataSources'] + for datasource in datasources: + datasourceDetails = datasource['dataSourceDetail'] + picName = datasourceDetails.get('picName', None) + self.assertIn(picName, ['pic test', None]) \ No newline at end of file diff --git a/sizakat/mustahik/types.py b/sizakat/mustahik/types.py index 2b1a7a25f7fdfd42fc97d2d8b658a5ca0afb66b3..a7e626b03b1d40f232290f8a5796d74f5bab0e1a 100644 --- a/sizakat/mustahik/types.py +++ b/sizakat/mustahik/types.py @@ -13,6 +13,9 @@ class MustahikType(DjangoObjectType): age = graphene.Int(source='calculate_age') + def resolve_photo(self, info, **kwargs): + return self.photo and info.context.build_absolute_uri(self.photo.url) + class DataSourceInstitusiType(DjangoObjectType): class Meta: diff --git a/sizakat/settings.py b/sizakat/settings.py index a3e25ad37cf703ea6f99f07e6d4ff0f3cbddde2d..7b2a30a59c6b9c6fae977081126a51bf42384b1e 100644 --- a/sizakat/settings.py +++ b/sizakat/settings.py @@ -141,5 +141,5 @@ USE_TZ = True STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') -MEDIA_URL = '/img/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'images') +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles')