From de5201e2c4b490318e44e4f189ae96dea42c85ea Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Fri, 1 Feb 2019 01:05:40 -0200 Subject: [PATCH 1/7] CHANGE geolocation em formato geoJson --- .../brumadinho/migrations/0001_initial.py | 28 ++++++++-- .../migrations/0002_auto_20190129_2219.py | 53 ------------------- .../migrations/0002_auto_20190201_0206.py | 25 +++++++++ .../migrations/0003_geolocation_radius.py | 18 ------- .../migrations/0004_auto_20190130_2213.py | 27 ---------- .../service/brumadinho/models.py | 13 +---- .../service/brumadinho/serializers.py | 22 +++++++- .../service/service/requirements.txt | 2 + .../service/service/settings.py | 13 +++-- 9 files changed, 82 insertions(+), 119 deletions(-) delete mode 100644 geolocations_service/service/brumadinho/migrations/0002_auto_20190129_2219.py create mode 100644 geolocations_service/service/brumadinho/migrations/0002_auto_20190201_0206.py delete mode 100644 geolocations_service/service/brumadinho/migrations/0003_geolocation_radius.py delete mode 100644 geolocations_service/service/brumadinho/migrations/0004_auto_20190130_2213.py 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..759f0eb 100644 --- a/geolocations_service/service/brumadinho/serializers.py +++ b/geolocations_service/service/brumadinho/serializers.py @@ -1,13 +1,31 @@ # 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_field = "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 class VisitedLocationSerializer(serializers.ModelSerializer): class Meta: 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'), } } From 6c4c4b84302be0b5199abbe053fe5730141a39a3 Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Sat, 2 Feb 2019 13:10:45 -0200 Subject: [PATCH 2/7] =?UTF-8?q?CHANGE=20coordinates=20=C3=A9=20somente=20l?= =?UTF-8?q?eitura=20em=20Geolocation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- geolocations_service/service/brumadinho/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geolocations_service/service/brumadinho/serializers.py b/geolocations_service/service/brumadinho/serializers.py index 759f0eb..c415d0c 100644 --- a/geolocations_service/service/brumadinho/serializers.py +++ b/geolocations_service/service/brumadinho/serializers.py @@ -15,7 +15,7 @@ class Meta: model = Geolocation fields = ("latitude", "longitude") geo_field = "coordinates" - read_only_field = "coordinates" + read_only_fields = ("coordinates",) write_only_fields = ("latitude", "longitude") def create(self, data): From 42c52ab0ebc27171453d43fb9a361715ce1b8477 Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Sun, 3 Feb 2019 01:17:08 -0200 Subject: [PATCH 3/7] ADD/FIX update geolocation --- .../service/brumadinho/serializers.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/geolocations_service/service/brumadinho/serializers.py b/geolocations_service/service/brumadinho/serializers.py index c415d0c..1165aee 100644 --- a/geolocations_service/service/brumadinho/serializers.py +++ b/geolocations_service/service/brumadinho/serializers.py @@ -26,6 +26,24 @@ def create(self, data): ) 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: From 6784bb978deee6caa29e5e0f9fe985d2fd0f437c Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Sun, 3 Feb 2019 01:17:21 -0200 Subject: [PATCH 4/7] ADD geolocation test --- .../brumadinho/tests/test_geolocation.py | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 geolocations_service/service/brumadinho/tests/test_geolocation.py 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 + ) From 3c9efc5facf4e8b15881aa77f1202966da809620 Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Sun, 3 Feb 2019 01:18:29 -0200 Subject: [PATCH 5/7] REMOVE tests file --- geolocations_service/service/brumadinho/tests.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 geolocations_service/service/brumadinho/tests.py diff --git a/geolocations_service/service/brumadinho/tests.py b/geolocations_service/service/brumadinho/tests.py deleted file mode 100644 index e69de29..0000000 From b1c597e0f03a951a5c952162d2205c037db256c5 Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Sun, 3 Feb 2019 01:28:27 -0200 Subject: [PATCH 6/7] ADD test command --- geolocations_service/service/Makefile | 3 +++ 1 file changed, 3 insertions(+) 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 From e3c398c4cf0998bb472ad48f215c0c518bdacba0 Mon Sep 17 00:00:00 2001 From: "Bruno L. Carli" Date: Sun, 3 Feb 2019 01:28:43 -0200 Subject: [PATCH 7/7] ADD test command to readme --- geolocations_service/service/README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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