diff --git a/geolocations_service/service/Makefile b/geolocations_service/service/Makefile index 0a8263e..4a1a5cb 100644 --- a/geolocations_service/service/Makefile +++ b/geolocations_service/service/Makefile @@ -1,2 +1,5 @@ run: python manage.py runserver 0.0.0.0:5002 + +test: + python manage.py test brumadinho.tests \ No newline at end of file diff --git a/geolocations_service/service/README.md b/geolocations_service/service/README.md index 99b778c..ce230bc 100644 --- a/geolocations_service/service/README.md +++ b/geolocations_service/service/README.md @@ -36,6 +36,18 @@ Instale os requerimentos para o servidor funcionar (preferencialmente em um ambi Acesse o servidor local em `localhost:5002/api/` +## Testes + +#### Rodar todos os testes +Para rodar os testes de maneira geral basta executar + + make test + +#### Rodar testes indiviualmente +Para rodar um testes específico execute o comando `python manage.py test brumadinho.tests.` onde `` é o nome do arquivo contendo o teste que deseja executar. i.e: + + python manage.py test brumadinho.tests.test_geolocation + ## HELP NEEDED Tem muita coisa que pode ser feita aqui ainda, toda ajuda é necessária. @@ -64,7 +76,7 @@ Clone repo to a workspace. Install requeriments (preferably in a virtual environment) ### Development - pip install -r service.requirements.develop.txt + pip install -r service.requirements.txt ### Migrate the database @@ -78,7 +90,17 @@ Install requeriments (preferably in a virtual environment) Acess local server at `localhost:5002/api/` +## Tests + +#### Run all tests +To run all tests just execute: + + make test + +#### Running tests individually +To run an single especific test execute `python manage.py test brumadinho.tests.` where `` is the filename which is the one you want to execute i.e: + python manage.py test brumadinho.tests.test_geolocation ## HELP NEEDED diff --git a/geolocations_service/service/brumadinho/migrations/0001_initial.py b/geolocations_service/service/brumadinho/migrations/0001_initial.py index ba27381..2de087d 100644 --- a/geolocations_service/service/brumadinho/migrations/0001_initial.py +++ b/geolocations_service/service/brumadinho/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 2.1.3 on 2019-01-29 02:11 +# Generated by Django 2.1.3 on 2019-01-31 23:29 +import django.contrib.gis.db.models.fields from django.db import migrations, models import django.db.models.deletion @@ -12,20 +13,37 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='FoundPeople', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('full_name', models.CharField(blank=True, help_text='Full name of the found people.', max_length=150)), + ('gender', models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], default=None, help_text='Gender of the found people.', max_length=2, null=True)), + ('status_condition', models.CharField(blank=True, choices=[('Alive', 'Alive'), ('Dead', 'Dead')], default=None, help_text='Status condition of the found people.', max_length=5, null=True)), + ], + ), migrations.CreateModel( name='Geolocation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('latitude', models.FloatField()), - ('longitude', models.FloatField()), + ('coordinates', django.contrib.gis.db.models.fields.PointField(srid=4326)), ], ), migrations.CreateModel( name='VisitedLocation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('reference', models.CharField(max_length=100)), - ('location', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='brumadinho.Geolocation')), + ('reference', models.CharField(help_text='Reference and information.', max_length=100)), + ('visitation_date', models.DateTimeField(blank=True, help_text='Datetime of the location analysis.', null=True)), + ('encounter_number', models.IntegerField(help_text='Number of people found in this location.')), + ('radius', models.FloatField(default=1, help_text='Radial área range of search.')), + ('observations', models.CharField(blank=True, help_text='Observations', max_length=2000, null=True)), + ('location', models.ForeignKey(help_text='Geoposition of the visited area.', on_delete=django.db.models.deletion.PROTECT, to='brumadinho.Geolocation')), ], ), + migrations.AddField( + model_name='foundpeople', + name='location', + field=models.ForeignKey(blank=True, help_text='Found location.', null=True, on_delete=django.db.models.deletion.PROTECT, to='brumadinho.Geolocation'), + ), ] diff --git a/geolocations_service/service/brumadinho/migrations/0002_auto_20190129_2219.py b/geolocations_service/service/brumadinho/migrations/0002_auto_20190129_2219.py deleted file mode 100644 index f4d0a27..0000000 --- a/geolocations_service/service/brumadinho/migrations/0002_auto_20190129_2219.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 2.1.3 on 2019-01-29 22:19 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('brumadinho', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='FoundPeople', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('full_name', models.CharField(blank=True, help_text='Full name of the found people.', max_length=150)), - ('gender', models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], default=None, help_text='Gender of the found people.', max_length=2, null=True)), - ('status_condition', models.CharField(blank=True, choices=[('Alive', 'Alive'), ('Dead', 'Dead')], default=None, help_text='Status condition of the found people.', max_length=5, null=True)), - ], - ), - migrations.AddField( - model_name='visitedlocation', - name='encounter_number', - field=models.IntegerField(default=0, help_text='Number of people found in this location.'), - preserve_default=False, - ), - migrations.AddField( - model_name='visitedlocation', - name='visitation_date', - field=models.DateTimeField(blank=True, help_text='Datetime of the location analysis.', null=True), - ), - migrations.AlterField( - model_name='visitedlocation', - name='location', - field=models.ForeignKey(help_text='Geoposition of the visited area.', on_delete=django.db.models.deletion.PROTECT, to='brumadinho.Geolocation'), - ), - migrations.AlterField( - model_name='visitedlocation', - name='reference', - field=models.CharField(help_text='Reference and information.', max_length=100), - ), - migrations.AlterUniqueTogether( - name='geolocation', - unique_together={('latitude', 'longitude')}, - ), - migrations.AddField( - model_name='foundpeople', - name='location', - field=models.ForeignKey(blank=True, help_text='Found location.', null=True, on_delete=django.db.models.deletion.PROTECT, to='brumadinho.Geolocation'), - ), - ] diff --git a/geolocations_service/service/brumadinho/migrations/0002_auto_20190201_0206.py b/geolocations_service/service/brumadinho/migrations/0002_auto_20190201_0206.py new file mode 100644 index 0000000..807a5ac --- /dev/null +++ b/geolocations_service/service/brumadinho/migrations/0002_auto_20190201_0206.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.3 on 2019-02-01 02:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('brumadinho', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='geolocation', + name='latitude', + field=models.FloatField(default=0.0), + preserve_default=False, + ), + migrations.AddField( + model_name='geolocation', + name='longitude', + field=models.FloatField(default=0.0), + preserve_default=False, + ), + ] diff --git a/geolocations_service/service/brumadinho/migrations/0003_geolocation_radius.py b/geolocations_service/service/brumadinho/migrations/0003_geolocation_radius.py deleted file mode 100644 index d59859f..0000000 --- a/geolocations_service/service/brumadinho/migrations/0003_geolocation_radius.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.1.3 on 2019-01-30 22:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('brumadinho', '0002_auto_20190129_2219'), - ] - - operations = [ - migrations.AddField( - model_name='geolocation', - name='radius', - field=models.FloatField(default=1, help_text='Radial área range.'), - ), - ] diff --git a/geolocations_service/service/brumadinho/migrations/0004_auto_20190130_2213.py b/geolocations_service/service/brumadinho/migrations/0004_auto_20190130_2213.py deleted file mode 100644 index 2076a2d..0000000 --- a/geolocations_service/service/brumadinho/migrations/0004_auto_20190130_2213.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.1.3 on 2019-01-30 22:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('brumadinho', '0003_geolocation_radius'), - ] - - operations = [ - migrations.RemoveField( - model_name='geolocation', - name='radius', - ), - migrations.AddField( - model_name='visitedlocation', - name='observations', - field=models.CharField(blank=True, help_text='Observations', max_length=2000, null=True), - ), - migrations.AddField( - model_name='visitedlocation', - name='radius', - field=models.FloatField(default=1, help_text='Radial área range of search.'), - ), - ] diff --git a/geolocations_service/service/brumadinho/models.py b/geolocations_service/service/brumadinho/models.py index 208435f..17463b8 100644 --- a/geolocations_service/service/brumadinho/models.py +++ b/geolocations_service/service/brumadinho/models.py @@ -1,21 +1,12 @@ from django.db import models - +from django.contrib.gis.db import models as gis_models class Geolocation(models.Model): - class Meta: - unique_together = ['latitude', 'longitude'] - + coordinates = gis_models.PointField(null=True, blank=True) latitude = models.FloatField() longitude = models.FloatField() - def __str__(self): - return str("lat: {} long: {}".format( - self.latitude, - self.longitude - )) - - class VisitedLocation(models.Model): reference = models.CharField( diff --git a/geolocations_service/service/brumadinho/serializers.py b/geolocations_service/service/brumadinho/serializers.py index 84867b5..1165aee 100644 --- a/geolocations_service/service/brumadinho/serializers.py +++ b/geolocations_service/service/brumadinho/serializers.py @@ -1,13 +1,49 @@ # from django.contrib.auth.models import Group from brumadinho.models import Geolocation, VisitedLocation, FoundPeople from rest_framework import serializers +from rest_framework_gis import serializers as gis_serializer +# from django.core.serializers import serialize +from django.contrib.gis.geos import Point -class GeolocationSerializer(serializers.ModelSerializer): +class GeolocationSerializer(gis_serializer.GeoFeatureModelSerializer): + + # TODO corrigir para que coordinates NÃO SEJA exibido como valor de input na tela + # nem seja possível de ser recebido na requisição. + class Meta: model = Geolocation - fields = "__all__" + fields = ("latitude", "longitude") + geo_field = "coordinates" + read_only_fields = ("coordinates",) + write_only_fields = ("latitude", "longitude") + + def create(self, data): + + data['coordinates'] = Point( + data.get('longitude'), + data.get('latitude') + ) + Geolocation.objects.create(**data) + return data + + def update(self, location, data): + + # geolocation = Geolocation.objects.get(id=location.id) + + latitude = data.get('latitude') + longitude = data.get('longitude') + + location.latitude = latitude + location.longitude = longitude + + location.coordinates = Point( + longitude, + latitude + ) + super(GeolocationSerializer, self).update(location, data) + return location class VisitedLocationSerializer(serializers.ModelSerializer): class Meta: diff --git a/geolocations_service/service/brumadinho/tests.py b/geolocations_service/service/brumadinho/tests.py deleted file mode 100644 index e69de29..0000000 diff --git a/geolocations_service/service/brumadinho/tests/test_geolocation.py b/geolocations_service/service/brumadinho/tests/test_geolocation.py new file mode 100644 index 0000000..234a000 --- /dev/null +++ b/geolocations_service/service/brumadinho/tests/test_geolocation.py @@ -0,0 +1,238 @@ +from rest_framework import status +from rest_framework.test import APITestCase +from brumadinho.models import Geolocation +from django.contrib.gis.geos import Point + + +class GeolocationTests(APITestCase): + def setUp(self): + ''' + Define a pré-inicialização dos modelos de teste. + ''' + + latitude = -134.99908 + longitude = 0.98217931 + + location = Geolocation.objects.create( + latitude=latitude, + longitude=longitude, + coordinates=Point(longitude, latitude) + ) + + def test_create_a_geolocation(self): + ''' + Teste que prova a criação de uma geolocalização + fornecendo latitude e longitude. + ''' + + url = '/api/geolocations/' + + latitude = -100.12345 + longitude = 0.987101 + + data = { + 'latitude': latitude, + 'longitude': longitude + } + response = self.client.post(url, data, format='json') + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['type'], 'Feature') + self.assertEqual(response.data['geometry']['type'], 'Point') + self.assertEqual( + response.data['geometry']['coordinates'], [longitude, latitude] + ) + self.assertEqual( + response.data['properties']['latitude'], latitude + ) + self.assertEqual( + response.data['properties']['longitude'], longitude + ) + + # verifica o banco de dados + created_object = Geolocation.objects.get( + latitude=latitude, + longitude=longitude + ) + self.assertEqual(Geolocation.objects.count(), 2) + self.assertEqual(created_object.latitude, latitude) + self.assertEqual(created_object.longitude, longitude) + + def test_create_a_geolocation_without_latitude(self): + ''' + Verifica que a tentativa de criar uma geolocalização + sem fornecer um valor de latitude falha, pois este é um + campo obrigatório. + ''' + + url = '/api/geolocations/' + expected_error = 'This field is required.' + + longitude = 0.987101 + + data = { + 'longitude': longitude + } + response = self.client.post(url, data, format='json') + error = str(response.data['latitude'][0]) + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(error, expected_error) + + # verifica o banco de dados + # deve haver somente um objeto, que foi criado durante o setUp + self.assertEqual(Geolocation.objects.count(), 1) + + def test_create_a_geolocation_without_longitude(self): + ''' + Verifica que a tentativa de criar uma geolocalização + sem fornecer um valor de longitude falha, pois este é um + campo obrigatório. + ''' + + url = '/api/geolocations/' + expected_error = 'This field is required.' + + latitude = 123.1238129812 + + data = { + 'latitude': latitude + } + response = self.client.post(url, data, format='json') + error = str(response.data['longitude'][0]) + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(error, expected_error) + + # verifica o banco de dados + # deve haver somente um objeto, que foi criado durante o setUp + self.assertEqual(Geolocation.objects.count(), 1) + + def test_create_a_geolocation_with_null_latitude(self): + ''' + Verifica que a tentativa de criar uma geolocalização + fornecendo null (None) para latitude falha, pois este é um + campo não nulo. + ''' + + url = '/api/geolocations/' + expected_error = 'This field may not be null.' + + longitude = 0.987101 + + data = { + 'latitude': None, + 'longitude': longitude + } + response = self.client.post(url, data, format='json') + error = str(response.data['latitude'][0]) + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(error, expected_error) + + # verifica o banco de dados + # deve haver somente um objeto, que foi criado durante o setUp + self.assertEqual(Geolocation.objects.count(), 1) + + def test_create_a_geolocation_with_null_longitude(self): + ''' + Verifica que a tentativa de criar uma geolocalização + fornecendo null (None) para longitude falha, pois este é um + campo não nulo. + ''' + + url = '/api/geolocations/' + expected_error = 'This field may not be null.' + + latitude = 0.987101 + + data = { + 'latitude': latitude, + 'longitude': None + } + response = self.client.post(url, data, format='json') + error = str(response.data['longitude'][0]) + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(error, expected_error) + + # verifica o banco de dados + # deve haver somente um objeto, que foi criado durante o setUp + self.assertEqual(Geolocation.objects.count(), 1) + + def test_update_a_geolocation(self): + ''' + Verifica a tentativa de atualizacão dos dados de uma + localização ja cadastrada. + ''' + + location = Geolocation.objects.get( + latitude=-134.99908, + longitude=0.98217931 + ) + url = '/api/geolocations/{}/'.format(location.id) + + new_lat = 90.99999 + new_long = 09.0909 + + data = { + 'latitude': new_lat, + 'longitude': new_long + } + response = self.client.put(url, data, format='json') + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['type'], 'Feature') + self.assertEqual(response.data['geometry']['type'], 'Point') + self.assertEqual( + response.data['geometry']['coordinates'], [new_long, new_lat] + ) + self.assertEqual( + response.data['properties']['latitude'], new_lat + ) + self.assertEqual( + response.data['properties']['longitude'], new_long + ) + + # verifica o banco de dados + updated_object = Geolocation.objects.get(id=location.id) + self.assertEqual(Geolocation.objects.count(), 1) + self.assertEqual(updated_object.latitude, new_lat) + self.assertEqual(updated_object.longitude, new_long) + + def test_get_geolocation(self): + ''' + Verifica a consulta à uma localização. + ''' + + location = Geolocation.objects.get( + latitude = -134.99908, + longitude = 0.98217931 + ) + + url = '/api/geolocations/{}/'.format(location.id) + + response = self.client.get(url, format='json') + + # verifica a resposta da requisição ao serviço + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['type'], 'Feature') + self.assertEqual(response.data['geometry']['type'], 'Point') + self.assertEqual( + response.data['geometry']['coordinates'], [ + location.longitude, + location.latitude + ] + ) + self.assertEqual( + response.data['properties']['latitude'], location.latitude + ) + self.assertEqual( + response.data['properties']['longitude'], location.longitude + ) diff --git a/geolocations_service/service/service/requirements.txt b/geolocations_service/service/service/requirements.txt index f930f85..957e1fb 100644 --- a/geolocations_service/service/service/requirements.txt +++ b/geolocations_service/service/service/requirements.txt @@ -1,3 +1,5 @@ django==2.1.5 djangorestframework==3.9.0 python-decouple==3.1 +djangorestframework-gis==0.14 +psycopg2==2.7.7 diff --git a/geolocations_service/service/service/settings.py b/geolocations_service/service/service/settings.py index 2471dd2..f15999f 100644 --- a/geolocations_service/service/service/settings.py +++ b/geolocations_service/service/service/settings.py @@ -24,8 +24,10 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.gis', 'rest_framework', - 'brumadinho' + 'rest_framework_gis', + 'brumadinho', ] MIDDLEWARE = [ @@ -67,8 +69,13 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + # 'ENGINE': 'django.db.backends.postgresql', + 'NAME': config('DB_NAME'), + 'USER': config('DB_USER'), + 'PASSWORD': config('DB_PASSWORD'), + 'HOST': config('DB_HOST'), + 'PORT': config('DB_PORT'), } }