Added all the functionality of app and admin
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import os
|
||||
import math
|
||||
from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
@@ -20,17 +23,19 @@ from ..models import (
|
||||
MealRecord,
|
||||
)
|
||||
|
||||
|
||||
class IAmPrincipalSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = IAmPrincipal
|
||||
fields = [
|
||||
# "profile_photo",
|
||||
"profile_photo",
|
||||
"first_name",
|
||||
"date_of_birth",
|
||||
"gender",
|
||||
"phone_no",
|
||||
]
|
||||
|
||||
|
||||
class PrincipalHealthDataSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PrincipalHealthData
|
||||
@@ -44,6 +49,7 @@ class PrincipalHealthDataSerializer(serializers.ModelSerializer):
|
||||
"eat_frequency",
|
||||
]
|
||||
|
||||
|
||||
class PrincipalAndHealthSerializer(serializers.ModelSerializer):
|
||||
ethenicity = serializers.CharField(read_only=True)
|
||||
weight = serializers.DecimalField(max_digits=5, decimal_places=2, read_only=True)
|
||||
@@ -52,6 +58,7 @@ class PrincipalAndHealthSerializer(serializers.ModelSerializer):
|
||||
exercise_frequency = serializers.CharField(read_only=True)
|
||||
sleep_duration = serializers.CharField(read_only=True)
|
||||
eat_frequency = serializers.CharField(read_only=True)
|
||||
profile_complete = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = IAmPrincipal
|
||||
@@ -62,8 +69,8 @@ class PrincipalAndHealthSerializer(serializers.ModelSerializer):
|
||||
"date_of_birth",
|
||||
"gender",
|
||||
"phone_no",
|
||||
"phone_verified",
|
||||
"email_verified",
|
||||
# "phone_verified",
|
||||
# "email_verified",
|
||||
"ethenicity",
|
||||
"weight",
|
||||
"height",
|
||||
@@ -71,27 +78,87 @@ class PrincipalAndHealthSerializer(serializers.ModelSerializer):
|
||||
"exercise_frequency",
|
||||
"sleep_duration",
|
||||
"eat_frequency",
|
||||
"profile_complete"
|
||||
]
|
||||
|
||||
def calculate_profile_completion(self, user):
|
||||
"""
|
||||
Calculates the profile completion percentage for a user based on the required fields.
|
||||
"""
|
||||
fields = self.fields
|
||||
try:
|
||||
# Retrieve the user profile from the database
|
||||
profile = IAmPrincipal.objects.get(id=user)
|
||||
try:
|
||||
# Retrieve the user's health data from the database
|
||||
health_data = PrincipalHealthData.objects.get(principal=profile)
|
||||
except PrincipalHealthData.DoesNotExist:
|
||||
# If health data doesn't exist, set health_data to None
|
||||
health_data = None
|
||||
|
||||
# Initialize a counter for completed fields
|
||||
completed_fields = sum(
|
||||
1
|
||||
for field in fields
|
||||
if (
|
||||
# If the field is in the user profile and the field value is not None, not an empty string, and not an instance of datetime.date
|
||||
(field in vars(profile) and vars(profile).get(field, '') and vars(profile).get(field) != datetime.date) or
|
||||
|
||||
# If health data exists, the field is in the user's health data, and the field value is not None, not an empty string, and not an instance of datetime.date
|
||||
(health_data and field in vars(health_data) and vars(health_data).get(field, '') and vars(health_data).get(field) != datetime.date)
|
||||
)
|
||||
)
|
||||
|
||||
except IAmPrincipal.DoesNotExist:
|
||||
# If the user profile doesn't exist, return 0
|
||||
return 0
|
||||
|
||||
# Calculate the total number of fields
|
||||
total_fields = len(fields) - 1 # Exclude profile_complete field
|
||||
|
||||
# Calculate the profile completion percentage
|
||||
completion_percentage = math.floor((completed_fields / total_fields) * 100)
|
||||
|
||||
# Return the profile completion percentage
|
||||
return completion_percentage
|
||||
|
||||
def get_image_url(self, obj, field_name, request):
|
||||
image_field = getattr(obj, field_name)
|
||||
if image_field:
|
||||
return request.build_absolute_uri(image_field.url)
|
||||
return ""
|
||||
else:
|
||||
# Return the URL of the default image from the static path
|
||||
default_image_path = os.path.join(
|
||||
settings.STATIC_URL, "img/default_profile.jpg"
|
||||
)
|
||||
return request.build_absolute_uri(default_image_path)
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
request = self.context.get("request")
|
||||
data["profile_photo"] = self.get_image_url(instance, "profile_photo", request)
|
||||
health_data = instance.health_data_principal
|
||||
if health_data:
|
||||
data['ethenicity'] = health_data.ethenicity
|
||||
data['weight'] = health_data.weight
|
||||
data['height'] = health_data.height
|
||||
data['gastrointestinal_health'] = health_data.gastrointestinal_health
|
||||
data['exercise_frequency'] = health_data.exercise_frequency
|
||||
data['sleep_duration'] = health_data.sleep_duration
|
||||
data['eat_frequency'] = health_data.eat_frequency
|
||||
data["profile_complete"] = self.calculate_profile_completion(request.user.id)
|
||||
if (
|
||||
hasattr(instance, "health_data_principal")
|
||||
and instance.health_data_principal
|
||||
):
|
||||
health_data = instance.health_data_principal
|
||||
data["ethenicity"] = health_data.ethenicity
|
||||
data["weight"] = health_data.weight
|
||||
data["height"] = health_data.height
|
||||
data["gastrointestinal_health"] = health_data.gastrointestinal_health
|
||||
data["exercise_frequency"] = health_data.exercise_frequency
|
||||
data["sleep_duration"] = health_data.sleep_duration
|
||||
data["eat_frequency"] = health_data.eat_frequency
|
||||
else:
|
||||
# If health_data_principal doesn't exist or is empty, set empty strings for all attributes
|
||||
data["ethenicity"] = ""
|
||||
data["weight"] = 0.00
|
||||
data["height"] = 0.00
|
||||
data["gastrointestinal_health"] = ""
|
||||
data["exercise_frequency"] = ""
|
||||
data["sleep_duration"] = ""
|
||||
data["eat_frequency"] = ""
|
||||
return data
|
||||
|
||||
|
||||
@@ -100,6 +167,7 @@ class IntoleranceSerializer(serializers.ModelSerializer):
|
||||
model = Intolerance
|
||||
fields = ["id", "name", "duration"]
|
||||
|
||||
|
||||
class SymptomsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Symptoms
|
||||
@@ -141,6 +209,7 @@ class BeverageRecordSerializer(serializers.ModelSerializer):
|
||||
"quantity_measure",
|
||||
]
|
||||
|
||||
|
||||
class MealRecordSerializer(serializers.ModelSerializer):
|
||||
food_records = FoodRecordSerializer(many=True)
|
||||
beverage_records = BeverageRecordSerializer(many=True)
|
||||
@@ -148,7 +217,15 @@ class MealRecordSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = MealRecord
|
||||
fields = ['id', 'date', 'time', 'meal_type', 'food_records', 'food_ingredient_records', 'beverage_records']
|
||||
fields = [
|
||||
"id",
|
||||
"date",
|
||||
"time",
|
||||
"meal_type",
|
||||
"food_records",
|
||||
"food_ingredient_records",
|
||||
"beverage_records",
|
||||
]
|
||||
|
||||
def create(self, validated_data):
|
||||
food_record_data = validated_data.pop("food_records", [])
|
||||
@@ -210,6 +287,7 @@ class MealRecordSerializer(serializers.ModelSerializer):
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class MedicineSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Medicine
|
||||
@@ -253,6 +331,7 @@ class BowelSerializer(serializers.ModelSerializer):
|
||||
"date",
|
||||
"time",
|
||||
"stool_type",
|
||||
"stool_name",
|
||||
"duration",
|
||||
"completeness_of_evacuation",
|
||||
"urgency",
|
||||
@@ -314,12 +393,8 @@ class MealSymptomRecordSerializer(serializers.ModelSerializer):
|
||||
return meal_symptom_record
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
instance.date = validated_data.get(
|
||||
"date", instance.date
|
||||
)
|
||||
instance.time = validated_data.get(
|
||||
"time", instance.time
|
||||
)
|
||||
instance.date = validated_data.get("date", instance.date)
|
||||
instance.time = validated_data.get("time", instance.time)
|
||||
instance.symptoms_description = validated_data.get(
|
||||
"symptoms_description", instance.symptoms_description
|
||||
)
|
||||
@@ -343,4 +418,4 @@ class MealSymptomRecordSerializer(serializers.ModelSerializer):
|
||||
|
||||
instance.save()
|
||||
|
||||
return instance
|
||||
return instance
|
||||
|
||||
@@ -5,6 +5,7 @@ from . import views
|
||||
urlpatterns = [
|
||||
|
||||
path("profile/", views.ProfileAPIView.as_view()),
|
||||
path("profile/complete/", views.ProfileCompleteAPIView.as_view()),
|
||||
|
||||
path("daily-records/", views.DailyRecordAPIView.as_view()),
|
||||
|
||||
@@ -26,4 +27,6 @@ urlpatterns = [
|
||||
path("meal/", views.MealAPIView.as_view()),
|
||||
path("meal/<int:pk>/", views.MealAPIView.as_view()),
|
||||
|
||||
path("report/", views.ReportAPIView.as_view()),
|
||||
|
||||
]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from django.db.models import Prefetch
|
||||
from module_project import constants
|
||||
from django.db.models import Prefetch, Count, Max, Min
|
||||
from module_project import constants, date_utils
|
||||
from module_project.utils import ApiResponse
|
||||
from module_iam.models import IAmPrincipal
|
||||
from ..models import (
|
||||
@@ -33,6 +33,8 @@ from .serializers import (
|
||||
PrincipalAndHealthSerializer,
|
||||
)
|
||||
|
||||
from module_project.service import OneSignalNotificationService
|
||||
|
||||
|
||||
class ProfileAPIView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
@@ -51,7 +53,6 @@ class ProfileAPIView(APIView):
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_404_NOT_FOUND, message=constants.RECORD_NOT_FOUND
|
||||
)
|
||||
print(f"object data is {obj}")
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
|
||||
def post(self, request):
|
||||
@@ -83,6 +84,8 @@ class ProfileAPIView(APIView):
|
||||
try:
|
||||
# with transaction.atomic(): # Ensure atomicity of database operations
|
||||
principal_instance = principal_serializer.save()
|
||||
principal_instance.register_complete = True
|
||||
principal_instance.save()
|
||||
|
||||
# Check if health data already exists for the principal
|
||||
health_data_instance, created = PrincipalHealthData.objects.get_or_create(
|
||||
@@ -96,14 +99,23 @@ class ProfileAPIView(APIView):
|
||||
return ApiResponse.success(message=constants.SUCCESS)
|
||||
|
||||
|
||||
class ProfileCompleteAPIView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
model = IAmPrincipal
|
||||
|
||||
def get(self, request):
|
||||
user = IAmPrincipal.objects.filter(id=request.user.id).update(register_complete=True)
|
||||
return ApiResponse.success(message=constants.SUCCESS)
|
||||
|
||||
class DailyRecordAPIView(APIView):
|
||||
|
||||
def serialize_record(self, record):
|
||||
time_obj = datetime.strptime(str(record.time), '%H:%M:%S')
|
||||
time_obj = datetime.strptime(str(record.time), "%H:%M:%S")
|
||||
return {
|
||||
"id": record.id,
|
||||
"date": record.date,
|
||||
"time": time_obj.strftime('%I:%M %p'),
|
||||
"time": time_obj.strftime("%I:%M %p"),
|
||||
# Add other fields as needed
|
||||
}
|
||||
|
||||
@@ -112,36 +124,40 @@ class DailyRecordAPIView(APIView):
|
||||
# date = datetime.now().date()
|
||||
|
||||
if not date:
|
||||
return ApiResponse.error(message=constants.FAILURE, errors="Date parameter is missing")
|
||||
return ApiResponse.error(
|
||||
message=constants.FAILURE, errors="Date parameter is missing"
|
||||
)
|
||||
|
||||
try:
|
||||
# Convert the date string to a datetime object
|
||||
date_obj = datetime.strptime(date, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
return ApiResponse.error(message=constants.FAILURE, errors="Invalid date format")
|
||||
return ApiResponse.error(
|
||||
message=constants.FAILURE, errors="Invalid date format"
|
||||
)
|
||||
|
||||
# Define prefetch related queries for filtering the record of paticular date of all related models
|
||||
meal_records_prefetch = Prefetch(
|
||||
"meal_principal",
|
||||
queryset=MealRecord.objects.filter(date=date),
|
||||
queryset=MealRecord.objects.filter(date=date, deleted=False),
|
||||
to_attr="filtered_meal_record",
|
||||
)
|
||||
|
||||
medication_prefetch = Prefetch(
|
||||
"medication_principal",
|
||||
queryset=Medication.objects.filter(date=date),
|
||||
queryset=Medication.objects.filter(date=date, deleted=False),
|
||||
to_attr="filtered_medication",
|
||||
)
|
||||
|
||||
bowel_prefetch = Prefetch(
|
||||
"bowel_principal",
|
||||
queryset=Bowel.objects.filter(date=date),
|
||||
queryset=Bowel.objects.filter(date=date, deleted=False),
|
||||
to_attr="filtered_bowel",
|
||||
)
|
||||
|
||||
meal_symptom_prefetch = Prefetch(
|
||||
"meal_symptom_principal",
|
||||
queryset=MealSymptomRecord.objects.filter(date=date),
|
||||
queryset=MealSymptomRecord.objects.filter(date=date, deleted=False),
|
||||
to_attr="filtered_meal_symptom",
|
||||
)
|
||||
|
||||
@@ -173,19 +189,17 @@ class DailyRecordAPIView(APIView):
|
||||
for record in principal.filtered_meal_symptom
|
||||
]
|
||||
|
||||
all_records = (serialized_symptom + serialized_meal_records + serialized_medication + serialized_bowel)
|
||||
all_records = (
|
||||
serialized_symptom
|
||||
+ serialized_meal_records
|
||||
+ serialized_medication
|
||||
+ serialized_bowel
|
||||
)
|
||||
|
||||
# all_records_sorted = sorted(all_records, key=lambda x: x["time"], reverse=True)
|
||||
all_records_sorted = sorted(
|
||||
all_records,
|
||||
key=lambda x: x["time"],
|
||||
reverse=True
|
||||
)
|
||||
all_records_sorted = sorted(all_records, key=lambda x: x["time"], reverse=True)
|
||||
|
||||
|
||||
return ApiResponse.success(
|
||||
message=constants.SUCCESS, data=all_records_sorted
|
||||
)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=all_records_sorted)
|
||||
|
||||
|
||||
class IntoleranceListCreateAPIView(APIView):
|
||||
@@ -615,3 +629,167 @@ class MealAPIView(APIView):
|
||||
return ApiResponse.success(
|
||||
message=constants.RECORD_DELETED, status=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class ReportAPIView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
model = MealRecord
|
||||
|
||||
def get_user(self):
|
||||
return self.request.user
|
||||
|
||||
def enough_records_exist(self, start_date, end_date):
|
||||
"""
|
||||
Check if at least 7 days of records exist within the date range.
|
||||
|
||||
This method calculates the minimum date by subtracting the required number of days
|
||||
(min_days_required - 1) from the end_date. It then filters the queryset based on the
|
||||
principal and date range. If any objects exist within this range, the method returns True,
|
||||
indicating that there are at least 7 days of records.
|
||||
|
||||
:param start_date: The initial date in the date range
|
||||
:type start_date: datetime.date
|
||||
:param end_date: The final date in the date range
|
||||
:type end_date: datetime.date
|
||||
:return: True if at least 7 days of records exist, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
min_days_required = 7
|
||||
current_date = start_date
|
||||
count = 0
|
||||
|
||||
while current_date <= end_date:
|
||||
if self.model.objects.filter(principal=self.get_user(), date=current_date).exists():
|
||||
count += 1
|
||||
if count >= min_days_required:
|
||||
return True
|
||||
else:
|
||||
count = 0 # Reset count if record is missing for any day
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
return False
|
||||
|
||||
def get_top_food_avoid(self, start_date, end_date):
|
||||
"""Get the top food to avoid."""
|
||||
food_counts = defaultdict(int)
|
||||
ingredient_counts = defaultdict(int)
|
||||
beverage_counts = defaultdict(int)
|
||||
|
||||
symptom_records = MealSymptomRecord.objects.filter(
|
||||
principal=self.get_user(), date__range=(start_date, end_date)
|
||||
)
|
||||
|
||||
for symptom_record in symptom_records:
|
||||
closest_meal = (
|
||||
MealRecord.objects.filter(
|
||||
principal=symptom_record.principal, date__lte=symptom_record.date
|
||||
)
|
||||
.order_by("-date", "-time")
|
||||
.first()
|
||||
)
|
||||
if closest_meal:
|
||||
for food_record in closest_meal.food_records.all():
|
||||
food_counts[food_record.name] += 1
|
||||
for ingredient_record in closest_meal.food_ingredient_records.all():
|
||||
ingredient_counts[ingredient_record.name] += 1
|
||||
for beverage_record in closest_meal.beverage_records.all():
|
||||
beverage_counts[beverage_record.beverage_type] += 1
|
||||
|
||||
# Sort the dictionaries by their values in descending order and getting only top 3 record
|
||||
food_counts = dict(
|
||||
sorted(food_counts.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
)
|
||||
ingredient_counts = dict(
|
||||
sorted(ingredient_counts.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
)
|
||||
beverage_counts = dict(
|
||||
sorted(beverage_counts.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
)
|
||||
|
||||
food_avoid = next(iter(food_counts), None)
|
||||
return food_avoid, food_counts, ingredient_counts, beverage_counts
|
||||
|
||||
def get_symptoms_frequency(self, start_date, end_date):
|
||||
"""Get the frequency of symptoms."""
|
||||
symptom_records = MealSymptomRecord.objects.filter(
|
||||
principal=self.get_user(), date__range=(start_date, end_date)
|
||||
).annotate(
|
||||
before_meal_count=Count("symptoms_before_meal"),
|
||||
after_meal_count=Count("symptoms_after_meal"),
|
||||
)
|
||||
symptoms_frequency = defaultdict(int)
|
||||
|
||||
for record in symptom_records:
|
||||
for symptom in record.symptoms_before_meal.all():
|
||||
symptoms_frequency[symptom.name] += record.before_meal_count
|
||||
for symptom in record.symptoms_after_meal.all():
|
||||
symptoms_frequency[symptom.name] += record.after_meal_count
|
||||
|
||||
sorted_symptoms_frequency = dict(
|
||||
sorted(symptoms_frequency.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
)
|
||||
|
||||
return sorted_symptoms_frequency
|
||||
|
||||
def get_stool_type_counts(self, start_date, end_date):
|
||||
"""Get the count of stool types."""
|
||||
stool_type_counts = (
|
||||
Bowel.objects.filter(
|
||||
principal=self.get_user(), date__range=(start_date, end_date)
|
||||
)
|
||||
.values("stool_type")
|
||||
.annotate(stool_type_count=Count("stool_type"))
|
||||
)
|
||||
stool_type_counts_dict = {
|
||||
item["stool_type"]: item["stool_type_count"] for item in stool_type_counts
|
||||
}
|
||||
|
||||
stool_type_counts_sort = dict(
|
||||
sorted(stool_type_counts_dict.items(), key=lambda x: x[1], reverse=True)[:3]
|
||||
)
|
||||
|
||||
highest_stool = next(iter(stool_type_counts_sort), None)
|
||||
return stool_type_counts_sort, highest_stool
|
||||
|
||||
def get(self, request):
|
||||
date_range = request.GET.get("date_range")
|
||||
start_date, end_date = date_utils.get_date_range(date_range)
|
||||
print(f"start date is {start_date}, end_date is {end_date}")
|
||||
|
||||
print(f"is dats exist {self.enough_records_exist(start_date, end_date)}")
|
||||
|
||||
if not self.enough_records_exist(start_date, end_date):
|
||||
return ApiResponse.error(
|
||||
message="No report is generated. Minimum Previous 7 days of records required."
|
||||
)
|
||||
|
||||
# Get top food to avoid
|
||||
food_avoid, food_counts, ingredient_counts, beverage_counts = (
|
||||
self.get_top_food_avoid(start_date, end_date)
|
||||
)
|
||||
|
||||
# Get symptoms frequency
|
||||
sorted_symptoms_frequency = self.get_symptoms_frequency(start_date, end_date)
|
||||
|
||||
# Get stool type counts
|
||||
stool_type_counts_sort, highest_stool = self.get_stool_type_counts(
|
||||
start_date, end_date
|
||||
)
|
||||
|
||||
nested_json = {
|
||||
"food_avoid": food_avoid,
|
||||
"same_food_avoid": {
|
||||
"food": food_counts,
|
||||
"ingredient": ingredient_counts,
|
||||
"beverage": beverage_counts,
|
||||
},
|
||||
"symptoms_frequency": sorted_symptoms_frequency,
|
||||
"highest_stool": highest_stool,
|
||||
"stool_type": stool_type_counts_sort,
|
||||
}
|
||||
print(f"nested_json data is {nested_json}")
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=nested_json)
|
||||
|
||||
Reference in New Issue
Block a user