import csv import logging from collections import defaultdict from datetime import datetime, timedelta import pandas as pd from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Count, Prefetch, Q from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse_lazy from django.views import generic from django_datatables_view.base_datatable_view import BaseDatatableView from module_iam import iam_constant, permission from module_iam.models import IAmPrincipal from module_project import constants, date_utils from module_project.utils import JsonResponseUtil from .forms import (ChronicConditionForm, IntoleranceForm, PastTreatmentForm, SymptomsForm, UploadFileForm) from .models import (BeverageRecord, Bowel, ChronicCondition, FoodIngredientRecord, FoodIngredintDataset, FoodRecord, Intolerance, MealRecord, MealSymptomRecord, Medication, PastTreatment, Symptoms) logger = logging.getLogger(__name__) class BaseView(generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_USER resource = iam_constant.RESOURCE_MANAGE_USER action = None template_name = None model = None context_objext_name = "obj" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["page_name"] = self.page_name context["principal_id"] = get_object_or_404(IAmPrincipal, id=self.kwargs.get("principal_id")) return context class BaseCreateOrUpdateView(permission.ResourcePermissionRequiredMixin, LoginRequiredMixin, generic.View): page_name = iam_constant.RESOURCE_MANAGE_USER resource = iam_constant.RESOURCE_MANAGE_USER page_title = None model = None template_name = "module_activity/base_add.html" form_class = None success_url = None error_message = "An error occurred while saving the data." def get_success_message(self): self.success_message = ( constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED ) return self.success_message def get_object(self): pk = self.kwargs.get("pk") return get_object_or_404(self.model, pk=pk) if pk else None def get_context_data(self, **kwargs): context = { "page_name": self.page_name, "page_title": self.page_title, "principal_id": self.kwargs.get("principal_id"), "operation": "Add" if not self.object else "Edit", } context.update(kwargs) # Include any additional context data passed to the view return context def get(self, request, *args, **kwargs): self.object = self.get_object() form = self.form_class(instance=self.object) context = self.get_context_data(form=form) return render(request, self.template_name, context=context) def post(self, request, *args, **kwargs): principal_id = kwargs.get("principal_id") self.object = self.get_object() form = self.form_class(request.POST, instance=self.object) if not form.is_valid(): print(form.errors) context = self.get_context_data(form=form) return render(request, self.template_name, context=context) form.save(principal_id=principal_id) messages.success(self.request, self.get_success_message()) success_url = reverse_lazy( self.success_url, kwargs={"principal_id": principal_id} ) return redirect(success_url) class BaseListJson(BaseDatatableView): model = Intolerance columns = ["id", "name", "duration", "active", "deleted"] order_columns = ["id", "name", "duration", "active", "deleted"] FILTER_ICONTAINS = "icontains" def get_filter_method(self): """Returns preferred filter method""" return self.FILTER_ICONTAINS def get_initial_queryset(self): principal_id = self.kwargs.get("principal_id") deleted_flag = self.request.GET.get("deleted_flag", None) return self.model.objects.filter(principal=principal_id, deleted=deleted_flag) def ordering(self, qs): print(f"request is {self.request.GET}") order = self.request.GET.get('order[0][dir]', None) if order: column_index = int(self.request.GET.get('order[0][column]', None)) - 1 order_column = self.order_columns[column_index] print(f"order column is {order_column}") if order == "asc": qs = qs.order_by(order_column) elif order == "desc": qs = qs.order_by("-" + order_column) return qs class BaseActionView(generic.View): model = None def post(self, request, *args, **kwargs): if self.model is None: raise NotImplementedError( "Subclasses of BaseActionView must define a 'model' attribute." ) action = request.POST.get("action") # 'archive', 'active', or 'unarchive' ids = request.POST.getlist("ids[]") # List of user IDs to perform action on active = request.POST.get("active") print(f"arhive action {action} and id is {ids} and active data is {active}") if action == "archive": # Update 'deleted' field to True for the selected users self.model.objects.filter(id__in=ids).update(deleted=True, active=False) message = "Record archived successfully." elif action == "active": # Update 'active' field to True for the selected users self.model.objects.filter(id__in=ids).update(active=active.capitalize()) message = "Record activated successfully." elif action == "unarchive": # Update 'deleted' field to False for the selected users self.model.objects.filter(id__in=ids).update(deleted=False) message = "Record unarchived successfully." else: return JsonResponseUtil.error(message="Invalid Action") return JsonResponseUtil.success(message=message) class BaseArchiveView(generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_USER template_name = None def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) data["principal_id"] = kwargs.get("principal_id") data["page_name"] = self.page_name return data class PopulateFoodIngredientView(permission.ResourcePermissionRequiredMixin, generic.View): # Set the page_name and resource page_name = iam_constant.RESOURCE_MANAGE_DASHBOARD resource = iam_constant.RESOURCE_MANAGE_DASHBOARD template_name = "module_activity/food_ingredient_form.html" model = FoodIngredintDataset form_class = UploadFileForm success_url = reverse_lazy("module_iam:dashboard") error_message = "An error occurred while saving the data." # Add page_name and operation to the context def get_context_data(self, **kwargs): context = { "page_name": self.page_name, "operation": "Add", } context.update(kwargs) # Include any additional context data passed to the view return context def get(self, request, *args, **kwargs): form = self.form_class() context = self.get_context_data(form=form) return render(request, self.template_name, context=context) def post(self, request, *args, **kwargs): print("Request data: ", request.POST) form = self.form_class(request.POST, request.FILES) if not form.is_valid(): print(form.errors) context = self.get_context_data(form=form) return render(request, self.template_name, context=context) uploaded_file = request.FILES['file'] # decoded_file = uploaded_file.read().decode('utf-8', errors='ignore').splitlines() # csv_reader = csv.reader(decoded_file) df = pd.read_excel(uploaded_file) # this loop is for insert the food dataset record in table for index, row in df.iterrows(): food_name = row['Food'].strip().capitalize() ingredients_str = row['Ingredients'].strip() ingredients = [ingredient.strip().capitalize() for ingredient in ingredients_str.split(',')] if self.model.objects.filter(food_name=food_name).exists(): continue print(f"{food_name} : {ingredients}") food = self.model(food_name=food_name, ingredients=ingredients) food.save() # this loop is for updating the existing record in the database # for index, row in df.iterrows(): # food_name = row['Food'].strip().capitalize() # ingredients_str = row['Ingredients'].strip() # ingredients = [ingredient.strip().capitalize() for ingredient in ingredients_str.split(',')] # exist_food = self.model.objects.filter(food_name=food_name) # if exist_food.exists(): # exist_food.update(ingredients=ingredients) # print(f"{food_name} : {ingredients}") messages.success(self.request, constants.RECORD_CREATED) return redirect(self.success_url) class IntoleranceView(BaseView): model = Intolerance template_name = "module_activity/intolerance_list.html" class CreateOrUpdateIntoleranceView(BaseCreateOrUpdateView): model = Intolerance page_title = "Intolerance" form_class = IntoleranceForm success_url = "module_activity:intolerance" class IntoleranceListJson(BaseListJson): model = Intolerance class IntoleranceActionView(BaseActionView): model = Intolerance class IntoleranceArchiveView(generic.TemplateView): template_name = "module_activity/intolerance_archive_list.html" class SymptomsView(BaseView): model = Symptoms template_name = "module_activity/symptoms_list.html" class CreateOrUpdateSymptomsView(BaseCreateOrUpdateView): model = Symptoms page_title = "Symptoms" form_class = SymptomsForm success_url = "module_activity:symptoms" class SymptomsListJson(BaseListJson): model = Symptoms class SymptomsActionView(BaseActionView): model = Symptoms class SymptomsArchiveView(generic.TemplateView): template_name = "module_activity/symptoms_archive_list.html" class PastTreatmentView(BaseView): model = PastTreatment template_name = "module_activity/past_treatment_list.html" class CreateOrUpdatePastTreatmentView(BaseCreateOrUpdateView): model = PastTreatment page_title = "Past Treatment" form_class = PastTreatmentForm success_url = "module_activity:past_treatment" class PastTreatmentListJson(BaseListJson): model = PastTreatment class PastTreatmentActionView(BaseActionView): model = PastTreatment class PastTreatmentArchiveView(generic.TemplateView): template_name = "module_activity/past_treatment_archive_list.html" class ChronicConditionView(BaseView): model = ChronicCondition template_name = "module_activity/chronic_conditon_list.html" class CreateOrUpdateChronicConditionView(BaseCreateOrUpdateView): model = ChronicCondition page_title = "Chronic Conditon/Disease" form_class = ChronicConditionForm success_url = "module_activity:chronic_condition" class ChronicConditionListJson(BaseListJson): model = ChronicCondition class ChronicConditionActionView(BaseActionView): model = ChronicCondition class ChronicConditionArchiveView(generic.TemplateView): template_name = "module_activity/chronic_condition_archive_list.html" class MealView(BaseView): template_name = "module_activity/meal_list.html" class MealListJsonView(BaseDatatableView): model = MealRecord columns = ["id", "date", "time", "meal_type"] order_columns = ["id", "date", "time", "meal_type"] FILTER_ICONTAINS = "icontains" def get_filter_method(self): """Returns preferred filter method""" return self.FILTER_ICONTAINS def get_initial_queryset(self): principal_id = self.kwargs.get("principal_id") deleted_flag = self.request.GET.get("deleted_flag", None) return self.model.objects.filter(principal=principal_id, deleted=deleted_flag) def ordering(self, qs): qs = super().ordering(qs) order = self.request.GET.get('order[0][dir]', None) if order: column_index = int(self.request.GET.get('order[0][column]', None)) - 1 order_column = self.order_columns[column_index] if order == "asc": qs = qs.order_by(order_column) elif order == "desc": qs = qs.order_by("-" + order_column) return qs class MedicationView(BaseView): template_name = "module_activity/medication_list.html" class MedicationListJsonView(BaseDatatableView): model = Medication columns = ["id", "date", "time"] order_columns = ["id", "date", "time"] def get_initial_queryset(self): principal_id = self.kwargs.get("principal_id") deleted_flag = self.request.GET.get("deleted_flag", None) return self.model.objects.filter(principal=principal_id, deleted=deleted_flag) def ordering(self, qs): order = self.request.GET.get('order[0][dir]', None) if order: column_index = int(self.request.GET.get('order[0][column]', None)) - 1 order_column = self.order_columns[column_index] if order == "asc": qs = qs.order_by(order_column) elif order == "desc": qs = qs.order_by("-" + order_column) return qs class BowelView(BaseView): template_name = "module_activity/bowel_list.html" class BowelListJsonView(BaseDatatableView): model = Bowel columns = ["id", "date", "time", "stool_type"] order_columns = ["id", "date", "time", "stool_type"] def get_initial_queryset(self): principal_id = self.kwargs.get("principal_id") deleted_flag = self.request.GET.get("deleted_flag", None) return self.model.objects.filter(principal=principal_id, deleted=deleted_flag) def ordering(self, qs): order = self.request.GET.get('order[0][dir]', None) if order: column_index = int(self.request.GET.get('order[0][column]', None)) - 1 order_column = self.order_columns[column_index] if order == "asc": qs = qs.order_by(order_column) elif order == "desc": qs = qs.order_by("-" + order_column) return qs class MealSymptomsView(BaseView): template_name = "module_activity/meal_symptoms_list.html" class MealSymptomsListJsonView(BaseDatatableView): model = MealSymptomRecord columns = ["id", "date", "time"] order_columns = ["id", "date", "time"] def get_initial_queryset(self): principal_id = self.kwargs.get("principal_id") deleted_flag = self.request.GET.get("deleted_flag", None) return self.model.objects.filter(principal=principal_id, deleted=deleted_flag) def ordering(self, qs): order = self.request.GET.get('order[0][dir]', None) if order: column_index = int(self.request.GET.get('order[0][column]', None)) - 1 order_column = self.order_columns[column_index] if order == "asc": qs = qs.order_by(order_column) elif order == "desc": qs = qs.order_by("-" + order_column) return qs class UserActivityRecordView(generic.View): def serialize_record(self, record): time_obj = datetime.strptime(str(record.time), "%H:%M:%S") return { "id": record.id, "date": record.date, "time": time_obj.strftime("%I:%M %p"), } def get(self, request, *args, **kwargs): try: principal_id = self.kwargs.get("principal_id") date_range = request.GET.get("date_range") if not date_range: return JsonResponseUtil.error(message="Date range parameter is missing") start_date, end_date = date_utils.get_date_range(date_range) # Retrieve data from different models meal_records = MealRecord.objects.filter( principal=principal_id, date__range=(start_date, end_date) ) medication_records = Medication.objects.filter( principal=principal_id, date__range=(start_date, end_date) ) bowel_records = Bowel.objects.filter(principal=principal_id, date__range=(start_date, end_date)) meal_symptom_records = MealSymptomRecord.objects.filter( principal=principal_id, date__range=(start_date, end_date) ) print(f"==================meal record {meal_records}") # Prepare combined results data = [] for record in meal_records: data.append({"type": "Meal", **self.serialize_record(record)}) for record in medication_records: data.append({"type": "Medication", **self.serialize_record(record)}) for record in bowel_records: data.append({"type": "Bowel", **self.serialize_record(record)}) for record in meal_symptom_records: data.append({"type": "Symptom", **self.serialize_record(record)}) all_records_sorted = sorted(data, key=lambda x: x["time"], reverse=True) response_data = { "recordsTotal": len(all_records_sorted), "recordsFiltered": len(all_records_sorted), "data": all_records_sorted, } return JsonResponse(response_data) except Exception as e: return JsonResponseUtil.error(message="Something went wrong", errors=str(e)) class MealDetialView(permission.ResourcePermissionRequiredMixin, LoginRequiredMixin, generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_USER resource = iam_constant.RESOURCE_MANAGE_USER template_name = "module_activity/meal_detail.html" model = MealRecord def get_record(self): id = self.kwargs.get("pk") meal_record = get_object_or_404( self.model.objects.prefetch_related( "food_records", "beverage_records", "food_ingredient_records" ), id=id, ) return meal_record def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["obj"] = self.get_record() context["page_name"] = self.page_name return context class MedicationDetailView(permission.ResourcePermissionRequiredMixin, LoginRequiredMixin, generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_USER resource = iam_constant.RESOURCE_MANAGE_USER template_name = "module_activity/medication_detail.html" model = Medication def get_record(self): id = self.kwargs.get("pk") obj = get_object_or_404(self.model.objects.prefetch_related("medicines"), id=id) return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["obj"] = self.get_record() context["page_name"] = self.page_name return context class BowelDetailView(permission.ResourcePermissionRequiredMixin, LoginRequiredMixin, generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_USER resource = iam_constant.RESOURCE_MANAGE_USER template_name = "module_activity/bowel_detail.html" model = Bowel def get_record(self): id = self.kwargs.get("pk") obj = get_object_or_404(self.model, id=id) print(f"obj data of bowel is {obj}") return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["obj"] = self.get_record() context["page_name"] = self.page_name return context class MealSymptomDetailView(permission.ResourcePermissionRequiredMixin, LoginRequiredMixin, generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_USER resource = iam_constant.RESOURCE_MANAGE_USER template_name = "module_activity/meal_symptom_details.html" model = MealSymptomRecord def get_record(self): pk = self.kwargs.get("pk") obj = get_object_or_404( MealSymptomRecord.objects.prefetch_related( "symptoms_before_meal", "symptoms_after_meal" ), id=pk, ) return obj def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["obj"] = self.get_record() context["page_name"] = self.page_name return context class ReportChartView(generic.View): def get(self, request, *args, **kwargs): current_year = int(self.request.GET.get("year")) monthly_counts = { 'Meal': [0] * 12, 'Medication': [0] * 12, 'Symptoms': [0] * 12, 'Bowel': [0] * 12, } for month in range(1, 13): start_date = datetime(current_year, month, 1) end_date = datetime(current_year, month + 1, 1) if month < 12 else datetime(current_year + 1, 1, 1) monthly_counts['Meal'][month - 1] = MealRecord.objects.filter(date__range=(start_date, end_date)).count() monthly_counts['Medication'][month - 1] = Medication.objects.filter(date__range=(start_date, end_date)).count() monthly_counts['Symptoms'][month - 1] = MealSymptomRecord.objects.filter(date__range=(start_date, end_date)).count() monthly_counts['Bowel'][month - 1] = Bowel.objects.filter(date__range=(start_date, end_date)).count() print(f"===========================================================data is {monthly_counts}") return JsonResponseUtil.success(message=constants.SUCCESS, data=monthly_counts) class ReportDataView(generic.View): model = MealRecord def get_user(self, *args, **kwargs): user = IAmPrincipal.objects.filter(id=self.kwargs.get("principal_id")).first() print(f"user is {user}") return user def enough_records_exist(self, start_date, end_date): min_days_required = 7 current_date = start_date count = 0 obj = self.model.objects.filter(principal=self.get_user()) while current_date <= end_date: if obj.filter(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 # Fetch symptom records with related meal records and related food/ingredient/beverage records symptom_records = MealSymptomRecord.objects.prefetch_related( Prefetch('related_meal__food_records', queryset=FoodRecord.objects.all()), Prefetch('related_meal__food_ingredient_records', queryset=FoodIngredientRecord.objects.all()), Prefetch('related_meal__beverage_records', queryset=BeverageRecord.objects.all()) ).filter( principal=self.get_user(), date__range=(start_date, end_date) ) # Loop through symptom records and count food, ingredient, and beverage occurrences for each related meal record for symptom_record in symptom_records: closest_meal = symptom_record.related_meal 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, *args, **kwargs): 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): print("report does not exist") return JsonResponseUtil.success( message="No report is generated. Minimum Previous 7 days of records required.", status=202 ) # 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 JsonResponseUtil.success(message=constants.SUCCESS, data=nested_json)