From ddced5b98ff1dfa1a970fd02a8813f8dc9a53831 Mon Sep 17 00:00:00 2001 From: bobbyvish Date: Mon, 15 Apr 2024 14:50:54 +0530 Subject: [PATCH] refactor: home api sort and datatable --- module_activity/api/views.py | 43 ++- module_activity/views.py | 39 ++- module_cms/views.py | 18 +- module_iam/context_processors.py | 11 - module_notification/views.py | 24 +- openfoodfact.py | 22 ++ requirements.txt | 6 + .../module_iam/profile_details_edit.html | 244 +++++++----------- 8 files changed, 199 insertions(+), 208 deletions(-) create mode 100644 openfoodfact.py diff --git a/module_activity/api/views.py b/module_activity/api/views.py index 7979a8f..ac22560 100644 --- a/module_activity/api/views.py +++ b/module_activity/api/views.py @@ -12,7 +12,7 @@ from module_project import constants, date_utils from module_project.service import OneSignalNotificationService from module_project.utils import ApiResponse -from ..models import (FoodIngredintDataset, Bowel, ChronicCondition, Intolerance, MealRecord, +from ..models import (BeverageRecord, FoodIngredientRecord, FoodIngredintDataset, Bowel, ChronicCondition, FoodRecord, Intolerance, MealRecord, MealSymptomRecord, Medication, PastTreatment, PrincipalHealthData, Symptoms) from .serializers import (FoodDatasetSerializer, FoodIngredientDatasetSerializer, BowelSerializer, ChronicConditionSerializer, @@ -103,6 +103,7 @@ class DailyRecordAPIView(APIView): "id": record.id, "date": record.date, "time": time_obj.strftime("%I:%M %p"), + "sort_time": time_obj, # Add other fields as needed } @@ -184,7 +185,7 @@ class DailyRecordAPIView(APIView): ) # 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["sort_time"], reverse=True) return ApiResponse.success(message=constants.SUCCESS, data=all_records_sorted) @@ -762,18 +763,38 @@ class ReportAPIView(APIView): ingredient_counts = defaultdict(int) beverage_counts = defaultdict(int) - symptom_records = MealSymptomRecord.objects.filter( + # symptom_records = MealSymptomRecord.objects.select_related("related_meal").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 = ( - MealRecord.objects.filter( - principal=symptom_record.principal, date__lte=symptom_record.date - ) - .order_by("-date", "-time") - .first() - ) + closest_meal = symptom_record.related_meal if closest_meal: for food_record in closest_meal.food_records.all(): food_counts[food_record.name] += 1 diff --git a/module_activity/views.py b/module_activity/views.py index c05e18b..79784b5 100644 --- a/module_activity/views.py +++ b/module_activity/views.py @@ -20,7 +20,7 @@ from module_project.utils import JsonResponseUtil from .forms import (ChronicConditionForm, IntoleranceForm, PastTreatmentForm, SymptomsForm, UploadFileForm) -from .models import (Bowel, ChronicCondition, FoodIngredintDataset, +from .models import (BeverageRecord, Bowel, ChronicCondition, FoodIngredientRecord, FoodIngredintDataset, FoodRecord, Intolerance, MealRecord, MealSymptomRecord, Medication, PastTreatment, Symptoms) @@ -611,7 +611,6 @@ class ReportDataView(generic.View): model = MealRecord def get_user(self, *args, **kwargs): - print(f"user id is {self.kwargs.get('principal_id')}") user = IAmPrincipal.objects.filter(id=self.kwargs.get("principal_id")).first() print(f"user is {user}") return user @@ -641,18 +640,38 @@ class ReportDataView(generic.View): ingredient_counts = defaultdict(int) beverage_counts = defaultdict(int) - symptom_records = MealSymptomRecord.objects.filter( + # 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 = ( - MealRecord.objects.filter( - principal=symptom_record.principal, date__lte=symptom_record.date - ) - .order_by("-date", "-time") - .first() - ) + closest_meal = symptom_record.related_meal if closest_meal: for food_record in closest_meal.food_records.all(): food_counts[food_record.name] += 1 diff --git a/module_cms/views.py b/module_cms/views.py index 5020572..9e95e42 100644 --- a/module_cms/views.py +++ b/module_cms/views.py @@ -38,25 +38,17 @@ class FaqListJson(BaseDatatableView): model = Faqs columns = ["id", "question", "answer", "active"] order_columns = ["id", "question", "answer", "active"] + FILTER_ICONTAINS = "icontains" + + def get_filter_method(self): + """Returns preferred filter method""" + return self.FILTER_ICONTAINS def get_initial_queryset(self): deleted_flag = self.request.GET.get('deleted_flag', None) return self.model.objects.filter(deleted=deleted_flag) - # def filter_queryset(self, qs): - # # Implement your custom filtering logic here - # print(f"request is {self.request.GET}") - # search_value = self.request.GET.get("search[value]", None) - # if search_value: - # qs = qs.filter( - # Q(id__icontains=search_value) - # | Q(question__icontains=search_value) - # | Q(answer__icontains=search_value) - # ) - - # return qs - def ordering(self, qs): order = self.request.GET.get('order[0][dir]', None) if order: diff --git a/module_iam/context_processors.py b/module_iam/context_processors.py index 604f2a0..959d091 100644 --- a/module_iam/context_processors.py +++ b/module_iam/context_processors.py @@ -60,14 +60,3 @@ def iam_constants_context(request): 'RESOURCE_IAM_ROLE': RESOURCE_IAM_ROLE, } } - - -def resource_permissions(request): - if request.user.is_authenticated: - resource_permissions = IAmPrincipal.objects.filter(id=request.user.id).values_list('principal_resource__name', flat=True) - else: - resource_permissions = [] - - return { - 'resource_permissions': resource_permissions, - } \ No newline at end of file diff --git a/module_notification/views.py b/module_notification/views.py index 737dabc..ea4564d 100644 --- a/module_notification/views.py +++ b/module_notification/views.py @@ -41,27 +41,15 @@ class NotificationListJsonView(BaseDatatableView): columns = ["id", "title", "message", "active", "timestamp"] order_columns = ["id", "title", "message", "active", "timestamp"] + FILTER_ICONTAINS = "icontains" + + def get_filter_method(self): + """Returns preferred filter method""" + return self.FILTER_ICONTAINS + def get_initial_queryset(self): deleted_flag = self.request.GET.get("deleted_flag", None) return self.model.objects.filter(deleted=deleted_flag) - - # def render_column(self, row, column): - # if column == "timestamp": - # return date_utils.format_date_to_string(row.timestamp) - # return super().render_column(row, column) - - # def filter_queryset(self, qs): - # # Implement your custom filtering logic here - # print(f"request is {self.request.GET}") - # search_value = self.request.GET.get("search[value]", None) - # if search_value: - # qs = qs.filter( - # Q(id__icontains=search_value) - # | Q(question__icontains=search_value) - # | Q(answer__icontains=search_value) - # ) - - # return qs def ordering(self, qs): order = self.request.GET.get('order[0][dir]', None) diff --git a/openfoodfact.py b/openfoodfact.py new file mode 100644 index 0000000..22a35c9 --- /dev/null +++ b/openfoodfact.py @@ -0,0 +1,22 @@ +import openfoodfacts +import json + +# User-Agent is mandatory +api = openfoodfacts.API(user_agent="Digest/1.0") + +# Search for pizza products +data = api.product.text_search("Rice Noodles") + +# Create filename (adjust as needed) +filename = "pizza_products.json" + +# Open the file in write mode (will create if non-existent) +with open(filename, "w") as json_file: + + # Convert data to JSON string (ensure proper indentation) + json_string = json.dumps(data, indent=4) + + # Write the JSON string to the file + json_file.write(json_string) + +print(f"Pizza product data saved to '{filename}'.") diff --git a/requirements.txt b/requirements.txt index f79eb2d..eec46f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +annotated-types==0.6.0 anyio==4.3.0 asgiref==3.7.2 certifi==2024.2.2 @@ -9,6 +10,7 @@ cryptography==42.0.5 defusedxml==0.7.1 Django==5.0.2 django-cors-headers==4.3.1 +django-crontab==0.7.1 django-datatables-view==1.20.0 django-debug-toolbar==4.3.0 django-environ==0.11.2 @@ -29,6 +31,7 @@ numpy==1.26.4 oauthlib==3.2.2 onesignal-python-api==2.0.2 onesignal-sdk==2.0.0 +openfoodfacts==0.2.1 openpyxl==3.1.2 packaging==23.2 pandas==2.2.1 @@ -36,6 +39,8 @@ phonenumbers==8.13.30 pillow==10.2.0 pluggy==1.4.0 pycparser==2.21 +pydantic==2.6.4 +pydantic_core==2.16.3 PyJWT==2.8.0 pytest==8.0.2 python-dateutil==2.9.0.post0 @@ -47,5 +52,6 @@ six==1.16.0 sniffio==1.3.1 sqlparse==0.4.4 tqdm==4.66.2 +typing_extensions==4.11.0 tzdata==2023.4 urllib3==2.2.1 diff --git a/templates/module_iam/profile_details_edit.html b/templates/module_iam/profile_details_edit.html index 13a84e0..91140b0 100644 --- a/templates/module_iam/profile_details_edit.html +++ b/templates/module_iam/profile_details_edit.html @@ -24,57 +24,6 @@
-
{% csrf_token %} {% include 'base_structure/includes/dynamic_template_form.html' with form=form %} @@ -112,111 +61,116 @@ FilePondPluginImageExifOrientation, FilePondPluginFileValidateSize, // FilePondPluginImageEdit - ); + ); - // Select the file input and use - // create() to turn it into a pond - const inputElement = document.getElementById('id_profile_photo'); - const pond = FilePond.create(inputElement, { - storeAsFile: true, - dropOnPage: true - }); - // Get the profile photo URL using Django template syntax - const profilePhotoUrl = "{% if form.profile_photo.value %}{{ form.profile_photo.value.url }}{% endif %}"; + // Select the file input and use + // create() to turn it into a pond + const inputElement = document.getElementById('id_profile_photo'); + const pond = FilePond.create(inputElement, { + storeAsFile: true, + dropOnPage: true + }); + // Get the profile photo URL using Django template syntax + const profilePhotoUrl = "{% if form.profile_photo.value %}{{ form.profile_photo.value.url }}{% endif %}"; - if (profilePhotoUrl) { - // If the URL exists, add the profile photo to FilePond - pond.addFile(profilePhotoUrl); - } + if (profilePhotoUrl) { + // If the URL exists, add the profile photo to FilePond + pond.addFile(profilePhotoUrl); + } - $(document).ready(function() { - // Add custom validation method to check for special characters - $.validator.addMethod("noSpecialChars", function(value, element) { - return /^[a-zA-Z\s]*$/.test(value); // Allow only letters and whitespace - }, "Please enter only letters and spaces."); + $(document).ready(function() { + // Add custom validation method to check for special characters + $.validator.addMethod("noSpecialChars", function(value, element) { + return /^[a-zA-Z\s]*$/.test(value); // Allow only letters and whitespace + }, "Please enter only letters and spaces."); - // Add custom validation method to check for starting with a letter - $.validator.addMethod("startsWithLetter", function(value, element) { - return /^[a-zA-Z]/.test(value); // Check if the value starts with a letter - }, "Please start with a letter."); + // Add custom validation method to check for starting with a letter + $.validator.addMethod("startsWithLetter", function(value, element) { + return /^[a-zA-Z]/.test(value); // Check if the value starts with a letter + }, "Please start with a letter."); - // Initialize form validation - $("#profileEditForm").validate({ - rules: { - first_name: { - required: true, - minlength: 2, - maxlength:15, - noSpecialChars: true, - startsWithLetter: true - }, - last_name: { - required: true, - minlength: 2, - maxlength: 15, - noSpecialChars: true, - startsWithLetter: true - }, - date_of_birth: { - required: true, - date: true, - dateISO: true, // Check for ISO date format (YYYY-MM-DD) - pattern: /^\d{4}-\d{2}-\d{2}$/ // Custom pattern for YYYY-MM-DD format - }, - gender: { - required: true - }, - phone_no: { - required: true, - digits: true, - minlength: 10, - maxlength: 10 - } + // Initialize form validation + $("#profileEditForm").validate({ + rules: { + first_name: { + required: true, + minlength: 2, + maxlength:15, + noSpecialChars: true, + startsWithLetter: true }, - messages: { - first_name: { - required: "Please enter your first name.", - minlength: "First name must be at least 2 characters.", - maxlength: "First name must not exceed 15 characters.", - noSpecialChars: "Please enter only letters and spaces.", - startsWithLetter: "First name must start with a letter." - }, - last_name: { - required: "Please enter your last name.", - minlength: "Last name must be at least 2 characters.", - maxlength: "First name must not exceed 15 characters.", - noSpecialChars: "Please enter only letters and spaces.", - startsWithLetter: "Last name must start with a letter." - }, - date_of_birth: { - required: "Please enter your date of birth.", - date: "Please enter a valid date in the format YYYY-MM-DD.", - dateISO: "Please enter a valid date in the format YYYY-MM-DD.", - pattern: "Please enter a valid date in the format YYYY-MM-DD." - }, - gender: { - required: "Please select your gender." - }, - phone_no: { - required: "Please enter your phone number.", - digits: "Please enter only digits.", - minlength: "Phone number must be 10 digits long.", - maxlength: "Phone number must be 10 digits long." - } + last_name: { + required: true, + minlength: 2, + maxlength: 15, + noSpecialChars: true, + startsWithLetter: true }, - errorElement: 'div', - errorPlacement: function(error, element) { - error.addClass('invalid-feedback'); - $(element).closest('.form-group').append(error); + date_of_birth: { + required: true, + date: true, + dateISO: true, // Check for ISO date format (YYYY-MM-DD) + pattern: /^\d{4}-\d{2}-\d{2}$/ // Custom pattern for YYYY-MM-DD format }, - highlight: function(element, errorClass, validClass) { - $(element).addClass('is-invalid').removeClass('is-valid'); + gender: { + required: true }, - unhighlight: function(element, errorClass, validClass) { - $(element).removeClass('is-invalid').addClass('is-valid'); + phone_no: { + required: true, + digits: true, + minlength: 10, + maxlength: 10 } - }); + }, + messages: { + first_name: { + required: "Please enter your first name.", + minlength: "First name must be at least 2 characters.", + maxlength: "First name must not exceed 15 characters.", + noSpecialChars: "Please enter only letters and spaces.", + startsWithLetter: "First name must start with a letter." + }, + last_name: { + required: "Please enter your last name.", + minlength: "Last name must be at least 2 characters.", + maxlength: "First name must not exceed 15 characters.", + noSpecialChars: "Please enter only letters and spaces.", + startsWithLetter: "Last name must start with a letter." + }, + date_of_birth: { + required: "Please enter your date of birth.", + date: "Please enter a valid date in the format YYYY-MM-DD.", + dateISO: "Please enter a valid date in the format YYYY-MM-DD.", + pattern: "Please enter a valid date in the format YYYY-MM-DD." + }, + gender: { + required: "Please select your gender." + }, + phone_no: { + required: "Please enter your phone number.", + digits: "Please enter only digits.", + minlength: "Phone number must be 10 digits long.", + maxlength: "Phone number must be 10 digits long." + } + }, + errorElement: 'div', + errorPlacement: function(error, element) { + error.addClass('invalid-feedback'); + $(element).closest('.form-group').append(error); + }, + highlight: function(element, errorClass, validClass) { + $(element).addClass('is-invalid').removeClass('is-valid'); + }, + unhighlight: function(element, errorClass, validClass) { + $(element).removeClass('is-invalid').addClass('is-valid'); + }, + submitHandler: function(form, event) { + event.preventDefault(); // Prevent default form submission on validation failure + // You can now perform additional actions before submitting the form (optional) + form.submit(); // Submit the form if validation passes (optional) + } }); + }); {% endblock %} \ No newline at end of file