diff --git a/api/admin.py b/api/admin.py index d3fa868..4e7764c 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,5 +1,9 @@ from django.contrib import admin -from .models import Subject, Test, Disease, Result +from .models import ( + Subject, + Test, + Disease, + Result, Location) class SubjectAdmin(admin.ModelAdmin): # control which columns appear in the list view @@ -34,10 +38,8 @@ class SubjectAdmin(admin.ModelAdmin): readonly_fields = ('date_archived', 'date_deleted', 'created_at', 'modified_at') class TestAdmin(admin.ModelAdmin): - # control which columns appear in the list view list_display = ('disease_id', 'subject') - # Fields for when adding a user add_fieldsets = ( (None, { 'classes': ('wide',), @@ -47,14 +49,6 @@ class TestAdmin(admin.ModelAdmin): )} ), ) - - # Fields for editing existing users, grouped in fieldsets - # fieldsets = ( - # (None, {'fields': ('email', 'password')}), - # ('Personal info', {'fields': ('name',)}), - # ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), - # ('Important dates', {'fields': ('last_login', 'date_joined')}), - # ) readonly_fields = ('created_at', 'modified_at') @@ -89,9 +83,25 @@ class ResultAdmin(admin.ModelAdmin): ) readonly_fields = ('created_at', 'modified_at') + +class LocationAdmin(admin.ModelAdmin): + list_display = ('id', 'latitude', 'longitude', 'subject') + add_fieldsets = ( + (None, { + # 'classes': ('wide',), + 'fields': ( + 'subject', + 'latitude', + 'longitude' + )} + ), + ) + + readonly_fields = ('created_at', 'modified_at') admin.site.register(Subject, SubjectAdmin) admin.site.register(Test, TestAdmin) admin.site.register(Disease, DiseaseAdmin) -admin.site.register(Result, ResultAdmin) \ No newline at end of file +admin.site.register(Result, ResultAdmin) +admin.site.register(Location, LocationAdmin) \ No newline at end of file diff --git a/api/serializers.py b/api/serializers.py index 5e4ac02..132524f 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -42,7 +42,7 @@ def create(self, validated_data): class LocationSerializer(serializers.ModelSerializer): class Meta: model = Location - fields = ['latitude', 'longitude', 'user'] + fields = ['latitude', 'longitude', 'subject'] class BulkLocationSerializer(serializers.Serializer): locations = LocationSerializer(many=True) diff --git a/api/views.py b/api/views.py index 23878df..66c67dc 100644 --- a/api/views.py +++ b/api/views.py @@ -1,11 +1,16 @@ +import random +from datetime import timedelta + from django.utils import timezone -from django.db.models import F +from django.db.models import F, Subquery, OuterRef, Count from django.core.paginator import Paginator +from django.db.models.functions import TruncDate from rest_framework import viewsets, status from rest_framework.response import Response from rest_framework.views import APIView -from rest_framework.permissions import IsAuthenticated, AllowAny +from rest_framework.permissions import AllowAny +from rest_framework.decorators import action from rest_framework.pagination import PageNumberPagination from rest_framework_simplejwt.tokens import RefreshToken @@ -143,6 +148,27 @@ def destroy(self, request, *args, **kwargs): return Response(status=status.HTTP_204_NO_CONTENT) + def get_subjects_most_recent_locations(self): + latest_locations = Location.objects.filter( + created_at=Subquery( + Location.objects.filter( + subject=OuterRef('subject') + ).order_by('-created_at').values('created_at')[:1] + ) + ).select_related('subject') + + return latest_locations + + @action(detail=False, methods=['get'], url_path='latest-locations') + def get_subjects_recent_locations(self, request): + locations = self.get_subjects_most_recent_locations() + serializer = LocationSerializer(locations, many=True) + + return Response({ + 'total': locations.count(), + 'data': serializer.data + }) + class LocationViewSet(viewsets.ModelViewSet): queryset = Location.objects.filter(date_deleted__isnull=True) serializer_class = LocationSerializer @@ -176,14 +202,56 @@ def destroy(self, request, *args, **kwargs): class OverviewView(APIView): def get(self, request): - stats = [{ + today = timezone.now().date() + week_ago = today - timedelta(days=6) + + # Get subjects count per day + subjects_per_day = ( + Subject.objects + .filter(created_at__date__gte=week_ago) + .annotate(date=TruncDate('created_at')) + .values('date') + .annotate(subject_count=Count('id')) + .order_by('date') + ) + + # Get tests count per day + tests_per_day = ( + Test.objects + .filter(created_at__date__gte=week_ago) + .annotate(date=TruncDate('created_at')) + .values('date') + .annotate(test_count=Count('id')) + .order_by('date') + ) + + # Create a list of all dates in the range + date_range = [(today - timedelta(days=x)) for x in range(6, -1, -1)] + + # Convert querysets to dictionaries for easier lookup + subjects_dict = {item['date']: item['subject_count'] for item in subjects_per_day} + tests_dict = {item['date']: item['test_count'] for item in tests_per_day} + + # Combine the results + weekly_stats = [ + { + 'date': date.strftime('%Y-%m-%d'), + 'day': date.strftime('%A'), + 'new_subjects': subjects_dict.get(date, 0), + 'tests_taken': tests_dict.get(date, 0) + } + for date in date_range + ] + + data = { 'num_subjects': Subject.objects.filter(date_deleted__isnull=True).count(), 'num_tests': Test.objects.count(), 'num_diseases': Disease.objects.count(), - 'num_users': User.objects.filter(date_deleted__isnull=True).count() - }] + 'num_users': User.objects.filter(date_deleted__isnull=True).count(), + 'weekly_stats': weekly_stats + } - return Response(stats) + return Response(data) class TheaPagination(PageNumberPagination): page_size = 10