Added all the functionality of app and admin

This commit is contained in:
bobbyvish
2024-03-11 14:48:48 +05:30
parent 69dbc56374
commit fd4aef5a40
92 changed files with 8931 additions and 716 deletions

View File

@@ -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

View File

@@ -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()),
]

View File

@@ -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)