From f83b9f9416bba6089b116d7f3e660b996b6758d1 Mon Sep 17 00:00:00 2001 From: Tinashe <70011086+tinashechiraya@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:34:38 +0200 Subject: [PATCH] Add search tag on sitewithobservations (#1089) * patch: swagger fake view check and safe observation pk retrieval * patch: add search tag to sites with observations api * patch: show user friendly errors * patch: add missing foward slash * patch: add missing foward slash --- .../DownloadObservationModal/index.tsx | 7 +- .../src/components/ScoreForm/index.tsx | 2 +- django_project/monitor/observation_views.py | 82 ++++++++++--------- django_project/monitor/site_views.py | 23 ++++-- 4 files changed, 61 insertions(+), 53 deletions(-) diff --git a/django_project/minisass_frontend/src/components/DownloadObservationModal/index.tsx b/django_project/minisass_frontend/src/components/DownloadObservationModal/index.tsx index 07aa9351..677e8bdd 100644 --- a/django_project/minisass_frontend/src/components/DownloadObservationModal/index.tsx +++ b/django_project/minisass_frontend/src/components/DownloadObservationModal/index.tsx @@ -27,7 +27,7 @@ interface DownloadObservationFormProps { dateRange: string[]; } -const DOWNLOAD_OBSERVATIONS_URL = globalVariables.baseUrl + '/monitor/observations/download-v2' +const DOWNLOAD_OBSERVATIONS_URL = globalVariables.baseUrl + '/monitor/observations/download-v2/' const DownloadObservationForm: React.FC = ({ isOpen, onClose, siteId, dateRange}) => { @@ -55,7 +55,6 @@ const DownloadObservationForm: React.FC = ({ isOpe useEffect(() => { setFormData({ ...formData, startDate: dayjs(dateRange[0]), endDate: dayjs(dateRange[1]) }); - console.log('provided date ranges ',dateRange) }, [dateRange]); const handleSubmit = (e: FormEvent) => { @@ -91,12 +90,12 @@ const DownloadObservationForm: React.FC = ({ isOpe } else { setIsError(true); setShowHeading(false); - setResponseMessage(JSON.stringify(response.data)); + setResponseMessage('Error failed to download Observations'); } } catch (error) { setIsError(true); setShowHeading(false); - setResponseMessage(error.message); + setResponseMessage('Error failed to download Observations'); } }; diff --git a/django_project/minisass_frontend/src/components/ScoreForm/index.tsx b/django_project/minisass_frontend/src/components/ScoreForm/index.tsx index d1f66fa6..c387e47e 100644 --- a/django_project/minisass_frontend/src/components/ScoreForm/index.tsx +++ b/django_project/minisass_frontend/src/components/ScoreForm/index.tsx @@ -103,7 +103,7 @@ const ScoreForm: React.FC = ({ onCancel, additionalData, setSide const parsedState = JSON.parse(storedState); const user_email = parsedState.userData.email; const user_level = await axios.get( - `${globalVariables.baseUrl}/authentication/api/user-profile/is-expert/${user_email}` + `${globalVariables.baseUrl}/authentication/api/user-profile/is-expert/${user_email}/` ); if(user_level.data.is_expert){ diff --git a/django_project/monitor/observation_views.py b/django_project/monitor/observation_views.py index 1b36af8f..73fa03ed 100644 --- a/django_project/monitor/observation_views.py +++ b/django_project/monitor/observation_views.py @@ -1,70 +1,59 @@ +# Standard library imports import json import multiprocessing -from queue import Queue import os +from queue import Queue from datetime import datetime from decimal import Decimal -import botocore -# dependencies for ai score calculations +# Third-party dependencies import numpy as np import tensorflow as tf +from tensorflow import keras from PIL import Image +import botocore +from minio import Minio +from minio.error import S3Error +# Django imports from django.conf import settings +from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.contrib.gis.geos import Point -from django.core.exceptions import PermissionDenied -from django.core.exceptions import ValidationError +from django.core.exceptions import PermissionDenied, ValidationError from django.db import transaction from django.db.models import Max -from django.http import Http404 -from django.http import JsonResponse +from django.http import Http404, JsonResponse from django.shortcuts import get_object_or_404 from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from minio import Minio -from minio.error import S3Error -from rest_framework import generics, mixins + +# Django REST framework imports +from rest_framework import generics, mixins, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet -from tensorflow import keras -from django.contrib.auth.models import User -from rest_framework import status - +# Project-specific imports (minisass and monitor) from minisass.models import GroupScores from minisass.utils import get_s3_client from minisass_authentication.models import UserProfile from minisass_authentication.permissions import IsAuthenticatedOrWhitelisted from monitor.models import ( - Observations, Sites, SiteImage, ObservationPestImage + Observations, Sites, SiteImage, ObservationPestImage ) from monitor.serializers import ( - ObservationsSerializer, - ObservationPestImageSerializer, - ObservationsAllFieldsSerializer + ObservationsSerializer, + ObservationPestImageSerializer, + ObservationsAllFieldsSerializer ) + def clear_tensorflow_session(): tf.keras.backend.clear_session() - -def get_observations_by_site(request, site_id, format=None): - try: - site = Sites.objects.get(gid=site_id) - observations = Observations.objects.filter(site=site) - serializer = ObservationsAllFieldsSerializer(observations, many=True) - - return JsonResponse({'status': 'success', 'observations': serializer.data}) - - except Sites.DoesNotExist as e: - return JsonResponse({'status': 'error', 'message': 'Site does not exist'}, status=404) - - # Use environment variables for Minio configuration minio_access_key = settings.MINIO_ACCESS_KEY minio_secret_key = settings.MINIO_SECRET_KEY @@ -89,6 +78,18 @@ def get_observations_by_site(request, site_id, format=None): ] +def get_observations_by_site(request, site_id, format=None): + try: + site = Sites.objects.get(gid=site_id) + observations = Observations.objects.filter(site=site) + serializer = ObservationsAllFieldsSerializer(observations, many=True) + + return JsonResponse({'status': 'success', 'observations': serializer.data}) + + except Sites.DoesNotExist as e: + return JsonResponse({'status': 'error', 'message': 'Site does not exist'}, status=404) + + def retrieve_file_from_minio(file_name): try: minio_client = Minio( @@ -97,7 +98,6 @@ def retrieve_file_from_minio(file_name): secret_key=minio_secret_key, secure=secure_connection ) - # Download the file from Minio file_path = os.path.join( settings.MINIO_ROOT, settings.MINIO_BUCKET, file_name) @@ -130,8 +130,6 @@ def retrieve_file_from_minio(file_name): else: model = None -# section for ai score calculations -# TODO move this into seperate file def classify_image(image): @@ -590,11 +588,17 @@ class ObservationImageViewSet( serializer_class = ObservationPestImageSerializer def get_queryset(self): - """Return queryset.""" - observation = get_object_or_404( - Observations, pk=self.kwargs['observation_pk'] - ) - return observation.observationpestimage_set.all() + """Return queryset.""" + # Short-circuit during schema generation for Swagger + if getattr(self, 'swagger_fake_view', False): + return ObservationPestImage.objects.none() + + observation_pk = self.kwargs.get('observation_pk') + if not observation_pk: + raise ValueError("Missing 'observation_pk' in URL") + + observation = get_object_or_404(Observations, pk=observation_pk) + return observation.observationpestimage_set.all() def destroy(self, request, *args, **kwargs): # TODO: diff --git a/django_project/monitor/site_views.py b/django_project/monitor/site_views.py index 99162b87..d53d0001 100644 --- a/django_project/monitor/site_views.py +++ b/django_project/monitor/site_views.py @@ -3,9 +3,19 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView -from monitor.models import SiteImage, Sites, Assessment, Observations, ObservationPestImage +from monitor.models import ( + SiteImage, + Sites, + Assessment, + Observations, + ObservationPestImage +) from django.contrib.gis.measure import D -from rest_framework.parsers import MultiPartParser, FormParser, JSONParser +from rest_framework.parsers import ( + MultiPartParser, + FormParser, + JSONParser +) from minisass.models import GroupScores from django.utils.dateparse import parse_date from drf_yasg.utils import swagger_auto_schema @@ -26,9 +36,7 @@ class SaveObservationImagesView(generics.CreateAPIView): serializer_class = ObservationPestImageSerializer def create(self, request, *args, **kwargs): - # Extract site ID from the URL observation_id = kwargs.get('observationId') - try: observation_id = int(observation_id) except ValueError: @@ -61,9 +69,7 @@ class SaveSiteImagesView(generics.CreateAPIView): serializer_class = SiteImageSerializer def create(self, request, *args, **kwargs): - # Extract site ID from the URL site_id = kwargs.get('site_id') - try: site_id = int(site_id) except ValueError: @@ -117,9 +123,7 @@ def list(self, request): return Response(serializer.data) def create(self, request, *args, **kwargs): - # Get the highest gid value highest_gid = Sites.objects.latest('gid').gid if Sites.objects.exists() else 0 - # Increment the gid value new_gid = highest_gid + 1 # Extract data from the request payload @@ -260,7 +264,8 @@ class SitesWithObservationsView(APIView): } ), 400: openapi.Response(description="Invalid date format"), - } + }, + tags=['Sites with Observations'] ) def get(self, request): start_date_str = request.query_params.get('start_date', None)