Skip to content

Commit

Permalink
Merge pull request #473 from ropable/master
Browse files Browse the repository at this point in the history
Extend v2 API endpoints, add unit tests
  • Loading branch information
ropable authored Aug 11, 2023
2 parents 2b2e852 + a7351dd commit 5803ca9
Show file tree
Hide file tree
Showing 19 changed files with 1,194 additions and 637 deletions.
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Prepare the base environment.
FROM python:3.10.10-slim-bullseye as builder_base_wastd
FROM python:3.10.12-slim-bookworm as builder_base_wastd
MAINTAINER asi@dbca.wa.gov.au
LABEL org.opencontainers.image.source https://github.com/dbca-wa/wastd

Expand All @@ -22,7 +22,7 @@ RUN curl -s https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
# Install Python libs using Poetry.
FROM builder_base_wastd as python_libs_wastd
WORKDIR /app
ENV POETRY_VERSION=1.2.2
ENV POETRY_VERSION=1.5.1
RUN pip install --upgrade pip && pip install "poetry==$POETRY_VERSION"
COPY poetry.lock pyproject.toml /app/
RUN poetry config virtualenvs.create false \
Expand All @@ -37,6 +37,7 @@ COPY wastd ./wastd
COPY wamtram ./wamtram
COPY tagging ./tagging
RUN python manage.py collectstatic --noinput

# Run the application as the www-data user.
USER www-data
EXPOSE 8080
Expand Down
194 changes: 172 additions & 22 deletions observations/api_v2.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,78 @@
from django.http import HttpResponseBadRequest
from wastd.utils import ListResourceView, DetailResourceView
from .models import (
Encounter,
Area,
Survey,
SurveyMediaAttachment,
Encounter,
AnimalEncounter,
TurtleNestEncounter,
MediaAttachment,
TurtleNestObservation,
TurtleHatchlingEmergenceObservation,
NestTagObservation,
TurtleNestDisturbanceObservation,
LoggerObservation,
HatchlingMorphometricObservation,
TurtleHatchlingEmergenceOutlierObservation,
LightSourceObservation,
)
from .serializers_v2 import (
EncounterSerializer,
AreaSerializer,
SurveySerializer,
SurveyMediaAttachmentSerializer,
EncounterSerializer,
AnimalEncounterSerializer,
TurtleNestEncounterSerializer,
MediaAttachmentSerializer,
TurtleNestObservationSerializer,
TurtleHatchlingEmergenceObservationSerializer,
NestTagObservationSerializer,
TurtleNestDisturbanceObservationSerializer,
LoggerObservationSerializer,
HatchlingMorphometricObservationSerializer,
TurtleHatchlingEmergenceOutlierObservationSerializer,
LightSourceObservationSerializer,
)


class AreaListResource(ListResourceView):
model = Area
serializer = AreaSerializer


class AreaDetailResource(DetailResourceView):
model = Area
serializer = AreaSerializer


class SurveyListResource(ListResourceView):
model = Survey
serializer = SurveySerializer


class SurveyDetailResource(DetailResourceView):
model = Survey
serializer = SurveySerializer


class SurveyMediaAttachmentListResource(ListResourceView):
model = SurveyMediaAttachment
serializer = SurveyMediaAttachmentSerializer


class SurveyMediaAttachmentDetailResource(DetailResourceView):
model = SurveyMediaAttachment
serializer = SurveyMediaAttachmentSerializer


class EncounterListResource(ListResourceView):
model = Encounter
serializer = EncounterSerializer

def get_queryset(self):
# FIXME: permissions checking per object.
return Encounter.objects.all(
return self.model.objects.all(
).prefetch_related(
'observer',
'reporter',
Expand All @@ -36,41 +87,140 @@ class EncounterDetailResource(DetailResourceView):
serializer = EncounterSerializer


class AreaListResource(ListResourceView):
model = Area
serializer = AreaSerializer
class AnimalEncounterListResource(EncounterListResource):
model = AnimalEncounter
serializer = AnimalEncounterSerializer


class AreaDetailResource(DetailResourceView):
model = Area
serializer = AreaSerializer
class AnimalEncounterDetailResource(EncounterDetailResource):
model = AnimalEncounter
serializer = AnimalEncounterSerializer


class SurveyListResource(ListResourceView):
model = Survey
serializer = SurveySerializer
class TurtleNestEncounterListResource(EncounterListResource):
model = TurtleNestEncounter
serializer = TurtleNestEncounterSerializer

def get_queryset(self):
# Filtering options.
queryset = super().get_queryset()
if 'nest_type' in self.request.GET and self.request.GET['nest_type']:
queryset = queryset.filter(nest_type=self.request.GET['nest_type'])
if 'species' in self.request.GET and self.request.GET['species']:
queryset = queryset.filter(nest_type=self.request.GET['species'])

class SurveyDetailResource(DetailResourceView):
model = Survey
serializer = SurveySerializer
return queryset


class SurveyMediaAttachmentListResource(ListResourceView):
model = SurveyMediaAttachment
serializer = SurveyMediaAttachmentSerializer
class TurtleNestEncounterDetailResource(EncounterDetailResource):
model = TurtleNestEncounter
serializer = TurtleNestEncounterSerializer


class SurveyMediaAttachmentDetailResource(DetailResourceView):
model = SurveyMediaAttachment
serializer = SurveyMediaAttachmentSerializer
class ObservationListResource(ListResourceView):

def dispatch(self, request, *args, **kwargs):
if 'encounter_id' in request.GET and request.GET['encounter_id']:
try:
int(request.GET['encounter_id'])
except:
return HttpResponseBadRequest()
return super().dispatch(request, *args, **kwargs)

def get_queryset(self):
queryset = super().get_queryset()

if 'encounter_id' in self.request.GET and self.request.GET['encounter_id']:
queryset = queryset.filter(encounter__pk=int(self.request.GET['encounter_id']))

return queryset

class MediaAttachmentListResource(ListResourceView):

class MediaAttachmentListResource(ObservationListResource):
model = MediaAttachment
serializer = MediaAttachmentSerializer


class MediaAttachmentDetailResource(DetailResourceView):
model = MediaAttachment
serializer = MediaAttachmentSerializer


class TurtleNestObservationListResource(ObservationListResource):
model = TurtleNestObservation
serializer = TurtleNestObservationSerializer


class TurtleNestObservationDetailResource(DetailResourceView):
model = TurtleNestObservation
serializer = TurtleNestObservationSerializer


class TurtleHatchlingEmergenceObservationListResource(ObservationListResource):
model = TurtleHatchlingEmergenceObservation
serializer = TurtleHatchlingEmergenceObservationSerializer


class TurtleHatchlingEmergenceObservationDetailResource(DetailResourceView):
model = TurtleHatchlingEmergenceObservation
serializer = TurtleHatchlingEmergenceObservationSerializer


class NestTagObservationListResource(ObservationListResource):
model = NestTagObservation
serializer = NestTagObservationSerializer


class NestTagObservationDetailResource(DetailResourceView):
model = NestTagObservation
serializer = NestTagObservationSerializer


class TurtleNestDisturbanceObservationListResource(ObservationListResource):
model = TurtleNestDisturbanceObservation
serializer = TurtleNestDisturbanceObservationSerializer


class TurtleNestDisturbanceObservationDetailResource(DetailResourceView):
model = TurtleNestDisturbanceObservation
serializer = TurtleNestDisturbanceObservationSerializer


class LoggerObservationListResource(ObservationListResource):
model = LoggerObservation
serializer = LoggerObservationSerializer


class LoggerObservationDetailResource(DetailResourceView):
model = LoggerObservation
serializer = LoggerObservationSerializer


class HatchlingMorphometricObservationListResource(ObservationListResource):
model = HatchlingMorphometricObservation
serializer = HatchlingMorphometricObservationSerializer


class HatchlingMorphometricObservationDetailResource(DetailResourceView):
model = HatchlingMorphometricObservation
serializer = HatchlingMorphometricObservationSerializer


class TurtleHatchlingEmergenceOutlierObservationListResource(ObservationListResource):
model = TurtleHatchlingEmergenceOutlierObservation
serializer = TurtleHatchlingEmergenceOutlierObservationSerializer


class TurtleHatchlingEmergenceOutlierObservationDetailResource(DetailResourceView):
model = TurtleHatchlingEmergenceOutlierObservation
serializer = TurtleHatchlingEmergenceOutlierObservationSerializer


class LightSourceObservationListResource(ObservationListResource):
model = LightSourceObservation
serializer = LightSourceObservationSerializer


class LightSourceObservationDetailResource(DetailResourceView):
model = LightSourceObservation
serializer = LightSourceObservationSerializer
2 changes: 1 addition & 1 deletion observations/lookups.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
("ntp-broome", "NTP Access DB Broome"),
("cet", "Cetacean strandings DB"),
("pin", "Pinniped strandings DB"),
("reconstructed", "Reconstructed by WAStD"),
("reconstructed", "Reconstructed automatically"),
)

SIGHTING_STATUS_CHOICES = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from django.core.management.base import BaseCommand
import logging
from observations.odk import import_turtle_track_or_nest, import_site_visit_start, import_site_visit_end
from observations.odk import (
import_turtle_track_or_nest,
import_site_visit_start,
import_site_visit_end,
import_marine_wildlife_incident,
)
from wastd.odk import get_auth_headers


class Command(BaseCommand):
help = 'Runs ETL scripts to download submissions related to Turtle Track/Nest survey data from ODK'
help = 'Runs ETL scripts to download Turtle Monitoring form submissions from ODK Central'

def add_arguments(self, parser):
parser.add_argument(
Expand All @@ -30,3 +35,6 @@ def handle(self, *args, **options):

logger.info('Downloading data from Site Visit End form, linking encounters')
import_site_visit_end(duration_hr=options['duration'], auth_headers=auth_headers)

logger.info('Downloading data from Marine Wildlife Incident form')
import_marine_wildlife_incident(auth_headers=auth_headers)
45 changes: 45 additions & 0 deletions observations/migrations/0008_auto_20230809_1310.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 3.2.20 on 2023-08-09 05:10

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('observations', '0007_delete_dugongmorphometricobservation'),
]

operations = [
migrations.RemoveField(
model_name='turtlehatchlingemergenceobservation',
name='hatchling_path_to_sea',
),
migrations.AlterField(
model_name='encounter',
name='encounter_type',
field=models.CharField(blank=True, choices=[('stranding', 'Stranding'), ('tagging', 'Tagging'), ('nest', 'Nest'), ('tracks', 'Tracks'), ('inwater', 'In water'), ('tag-management', 'Tag Management'), ('logger', 'Logger'), ('other', 'Other')], default='other', editable=False, help_text='The primary concern of this encounter.', max_length=300, null=True, verbose_name='Encounter type'),
),
migrations.AlterField(
model_name='encounter',
name='source',
field=models.CharField(choices=[('direct', 'Direct entry'), ('paper', 'Paper data sheet'), ('odk', 'OpenDataKit mobile data capture'), ('wamtram', 'WAMTRAM 2 tagging DB'), ('ntp-exmouth', 'NTP Access DB Exmouth'), ('ntp-broome', 'NTP Access DB Broome'), ('cet', 'Cetacean strandings DB'), ('pin', 'Pinniped strandings DB'), ('reconstructed', 'Reconstructed automatically')], db_index=True, default='direct', help_text='Where was this record captured initially?', max_length=300, verbose_name='Data Source'),
),
migrations.AlterField(
model_name='nesttagobservation',
name='status',
field=models.CharField(choices=[('applied-new', 'Applied new'), ('resighted', 'Re-sighted associated with nest')], default='resighted', help_text='The status this tag was seen in, or brought into.', max_length=300, verbose_name='Tag status'),
),
migrations.AlterField(
model_name='survey',
name='source',
field=models.CharField(choices=[('direct', 'Direct entry'), ('paper', 'Paper data sheet'), ('odk', 'OpenDataKit mobile data capture'), ('wamtram', 'WAMTRAM 2 tagging DB'), ('ntp-exmouth', 'NTP Access DB Exmouth'), ('ntp-broome', 'NTP Access DB Broome'), ('cet', 'Cetacean strandings DB'), ('pin', 'Pinniped strandings DB'), ('reconstructed', 'Reconstructed automatically')], default='direct', help_text='Where was this record captured initially?', max_length=300, verbose_name='Data Source'),
),
migrations.AlterField(
model_name='surveyend',
name='source',
field=models.CharField(choices=[('direct', 'Direct entry'), ('paper', 'Paper data sheet'), ('odk', 'OpenDataKit mobile data capture'), ('wamtram', 'WAMTRAM 2 tagging DB'), ('ntp-exmouth', 'NTP Access DB Exmouth'), ('ntp-broome', 'NTP Access DB Broome'), ('cet', 'Cetacean strandings DB'), ('pin', 'Pinniped strandings DB'), ('reconstructed', 'Reconstructed automatically')], default='direct', help_text='Where was this record captured initially?', max_length=300, verbose_name='Data Source'),
),
migrations.DeleteModel(
name='PathToSea',
),
]
Loading

0 comments on commit 5803ca9

Please sign in to comment.