Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/apis/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
path("cases/", include("api.cases.urls", "cases")),
path("files/", include("api.files.urls", "files")),
path("notifications/", include("api.notifications.urls", "notifications")),
path("locations/", include("api.locations.urls", "locations")),
]
23 changes: 15 additions & 8 deletions api/cases/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from api.apis.pagination import LimitOffsetPagination, get_paginated_response
from api.cases.models import Case
from api.cases.selectors import get_case, list_case, list_case_match
from api.cases.services import create_case
from api.cases.services import create_case, publish_case
from api.common.utils import inline_serializer
from api.users.models import User


class CreateCaseApi(APIView):
class InputSerializer(serializers.Serializer):
type = serializers.CharField()
thumbnail = serializers.URLField()
photos_urls = serializers.ListField(child=serializers.URLField())
location = inline_serializer(
fields={
Expand Down Expand Up @@ -44,7 +44,7 @@ class InputSerializer(serializers.Serializer):
def post(self, request):
serializer = self.InputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
create_case(user=User.objects.all()[0], **serializer.validated_data)
create_case(user=request.user, **serializer.validated_data)

return Response(status=status.HTTP_201_CREATED)

Expand All @@ -66,15 +66,15 @@ class OutputSerializer(serializers.Serializer):
id = serializers.IntegerField()
type = serializers.CharField()
name = serializers.CharField(source="details.name")
thumbnail = serializers.URLField()
last_seen = serializers.DateField(source="details.last_seen")
posted_at = serializers.DateTimeField()
location = inline_serializer(
fields={
"gov": serializers.CharField(source="gov.name_ar"),
"city": serializers.CharField(source="city.name_ar"),
}
)
photos = serializers.ListField(source="photo_urls")
last_seen = serializers.DateField(source="details.last_seen")
posted_at = serializers.DateTimeField()

def get(self, request):
# Make sure the filters are valid, if passed
Expand All @@ -94,7 +94,7 @@ def get(self, request):

class DetailsCaseApi(APIView):
class OutputSerializer(serializers.Serializer):
user = serializers.IntegerField()
user = serializers.CharField(source="user.username")
type = serializers.CharField()
state = serializers.CharField(source="get_state_display")
photos = serializers.ListField(source="photo_urls")
Expand Down Expand Up @@ -138,7 +138,7 @@ def get(self, request, case_id):
# Fetching our case
case = get_case(pk=case_id, fetched_by=request.user)

# Selecting which cases to serialize depding on case type
# Selecting which cases to serialize depending on case type
case_source = "missing" if case.type == Case.Types.FOUND else "found"

# Writing our serializer here because of case source decision
Expand Down Expand Up @@ -169,3 +169,10 @@ class OutputSerializer(serializers.Serializer):
serializer = OutputSerializer(matches, many=True)

return Response(serializer.data)


class CasePublishApi(APIView):
def get(self, request, case_id):
case = get_case(case_id)
publish_case(case=case, performed_by=request.user)
return Response(status=status.HTTP_200_OK)
19 changes: 19 additions & 0 deletions api/cases/migrations/0008_case_thumbnail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.2.13 on 2022-05-12 12:33

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cases', '0007_auto_20220510_1459'),
]

operations = [
migrations.AddField(
model_name='case',
name='thumbnail',
field=models.URLField(default='https://picsum.photos/200/300'),
preserve_default=False,
),
]
7 changes: 1 addition & 6 deletions api/cases/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Types(models.TextChoices):
updated_at = models.DateTimeField(auto_now=True)
posted_at = models.DateTimeField(null=True, default=None, blank=True)
is_active = models.BooleanField(default=False, editable=False)
# TODO thumbnail
thumbnail = models.URLField()

@property
def photo_urls(self):
Expand All @@ -50,10 +50,6 @@ def __str__(self):
# Pass the case to the model then add matched cases if any.
@transition(field=state, source=States.PENDING, target=States.ACTIVE)
def activate(self):
from .services import case_matching_binding, process_case

matches = process_case(self)
case_matching_binding(matches)
self.is_active = True

# If User selected one of the matches to be the correct one
Expand All @@ -73,7 +69,6 @@ def archive(self):
def activate_again(self):
self.is_active = True

# FIXME
def publish(self):
self.posted_at = timezone.now()

Expand Down
69 changes: 64 additions & 5 deletions api/cases/services.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from datetime import date
from typing import Dict, List, Optional

from django.core.exceptions import PermissionDenied
from django.db import transaction
from firebase_admin.messaging import Message
from firebase_admin.messaging import Notification as FirebaseNotification
from rest_framework.exceptions import ValidationError

from api.locations.models import Location
from api.locations.services import create_location
from api.notifications.models import Notification
from api.notifications.services import create_notification
from api.users.models import User

from .models import Case, CaseDetails, CaseMatch, CasePhoto
Expand All @@ -28,10 +34,11 @@ def create_case(
user: User,
location: Dict,
details: Dict,
thumbnail: str,
photos_urls: List[str],
) -> Case:
location: Location = create_location(**location)
case = Case(type=type.upper(), user=user, location=location)
case = Case(type=type.upper(), user=user, location=location, thumbnail=thumbnail)

case.full_clean()
case.save()
Expand All @@ -41,6 +48,10 @@ def create_case(

create_case_details(case=case, **details)

# TODO Factor out to an async function
activate_case(case)
case.save()

return case


Expand Down Expand Up @@ -86,23 +97,24 @@ def create_case_match(*, missing: Case, found: Case, score: int) -> CaseMatch:
return case_match


def process_case(*, case: Case) -> List[Dict[int, int]]:
def process_case(case: Case) -> List[Dict[int, int]]:
"""
Send case id and photos to the machine learing model then
recives list of ids & scores that matched with the case
"""
...
return []


def case_matching_binding(*, case: Case, matches_list: List[Dict[int, int]]) -> None:
"""
Bind the processed case with it's matches by instaniating CaseMatch objects
"""
if not matches_list:
return

cases_ids = [match["id"] for match in matches_list]
cases_scores = [match["score"] for match in matches_list]
matches: List[Case] = Case.objects.filter(id__in=cases_ids)
if not matches:
return

missing = True if case.type == CaseType.MISSING else False

Expand All @@ -111,3 +123,50 @@ def case_matching_binding(*, case: Case, matches_list: List[Dict[int, int]]) ->
create_case_match(missing=case, found=match, score=score)
else:
create_case_match(missing=match, found=case, score=score)


def activate_case(case: Case):
matches = process_case(case)
case_matching_binding(case=case, matches_list=matches)
# TODO success or failure notification
create_notification(
title="تم رفع الحاله بنجاح",
body="جارى البحث عن المفقود وسنقوم بإشعارك فى حاله العثور لأى نتائج",
level=Notification.Level.INFO,
sent_to=case.user,
)

msg = Message(
notification=FirebaseNotification(
title="تم رفع الحاله بنجاح",
body="جارى البحث عن المفقود وسنقوم بإشعارك فى حاله العثور لأى نتائج",
)
)

case.user.fcmdevice.send_message(msg)


def publish_case(*, case: Case, performed_by: User):
if case.user != performed_by:
raise PermissionDenied()

if not case.is_active:
raise ValidationError("Cannot publish inactive case")

case.publish()
case.save()

create_notification(
title="تم نشر الحاله بنجاح",
body="تم نشر بيانات المعثور عليه بنجاح انتظر منا اشعار اخر فى حين الوصول لأى نتائج",
level=Notification.Level.SUCCESS,
sent_to=case.user,
)

msg = Message(
notification=FirebaseNotification(
title="تم نشر الحاله بنجاح",
body="تم نشر بيانات المعثور عليه بنجاح انتظر منا اشعار اخر فى حين الوصول لأى نتائج",
)
)
case.user.fcmdevice.send_message(msg)
9 changes: 8 additions & 1 deletion api/cases/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from django.urls import path

from .apis import CaseListApi, CaseMatchListApi, CreateCaseApi, DetailsCaseApi
from .apis import (
CaseListApi,
CaseMatchListApi,
CasePublishApi,
CreateCaseApi,
DetailsCaseApi,
)

app_name = "cases"

Expand All @@ -10,4 +16,5 @@
path("create/", CreateCaseApi.as_view(), name="create"),
# path("<int:case_id>/update/", UpdateCaseApi.as_view(), name="update"),
path("<int:case_id>/matches/", CaseMatchListApi.as_view(), name="matches"),
path("<int:case_id>/publish/", CasePublishApi.as_view(), name="publish"),
]
43 changes: 43 additions & 0 deletions api/locations/apis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from rest_framework import permissions, serializers
from rest_framework.response import Response
from rest_framework.views import APIView

from api.locations.selectors import list_governorate, list_governorate_cities


class GovernorateListApi(APIView):
permission_classes = [permissions.AllowAny]

class OutputSerializer(serializers.Serializer):
id = serializers.IntegerField()
name_ar = serializers.CharField()
name_en = serializers.CharField()

def get(self, request):

# Listing all user cases
govs = list_governorate()

# Serializing the results
serializer = self.OutputSerializer(govs, many=True)

return Response(serializer.data)


class GovernorateCitiesListApi(APIView):
permission_classes = [permissions.AllowAny]

class OutputSerializer(serializers.Serializer):
id = serializers.IntegerField()
name_ar = serializers.CharField()
name_en = serializers.CharField()

def get(self, request, gov_id):

# Listing all user cases
cities = list_governorate_cities(gov_id)

# Serializing the results
serializer = self.OutputSerializer(cities, many=True)

return Response(serializer.data)
6 changes: 4 additions & 2 deletions api/locations/selectors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.db.models.query import QuerySet
from django.shortcuts import get_object_or_404

from .models import City, Governorate

Expand All @@ -7,5 +8,6 @@ def list_governorate() -> QuerySet[Governorate]:
return Governorate.objects.all()


def list_cities() -> QuerySet[City]:
return City.objects.all()
def list_governorate_cities(gov_id) -> QuerySet[City]:
gov = get_object_or_404(Governorate, pk=gov_id)
return gov.cities.all()
14 changes: 14 additions & 0 deletions api/locations/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.urls import path

from .apis import GovernorateCitiesListApi, GovernorateListApi

app_name = "locations"

urlpatterns = [
path("governorates/", GovernorateListApi.as_view(), name="Governorates"),
path(
"governorates/<int:gov_id>/cities",
GovernorateCitiesListApi.as_view(),
name="Governorate_cities",
),
]
1 change: 0 additions & 1 deletion api/locations/views.py

This file was deleted.

1 change: 1 addition & 0 deletions api/notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Meta:
db_table = "notifications"
verbose_name = "notification"
verbose_name_plural = "notifications"
ordering = ["-created_at"]

def __str__(self) -> str:
return f"<Notification({self.level}): {self.id}>"
2 changes: 1 addition & 1 deletion api/notifications/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def create_notification(
title: str,
body: str,
level: str,
sent_to: FCMDevice,
sent_to: User,
) -> Notification:

notification = Notification(
Expand Down
Loading