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

42
apple.py Normal file
View File

@@ -0,0 +1,42 @@
import jwt
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.contrib.auth import get_user_model
from .utils import generate_token_and_user_data
User = get_user_model()
@api_view(['POST'])
def signin_apple(request):
try:
id_token = request.data['id_token']
email = request.data['email']
full_name = request.data['full_name']
# Verify the JWT token
header = {'alg': 'ES256', 'kid': 'YOUR_APPLE_KEY_ID'}
key = open('path/to/your/Apple-developer-cert.p8', 'rb').read()
decoded_token = jwt.decode(id_token, key, audience='YOUR_APP_BUNDLE_ID', algorithms=['ES256'], options={'verify_aud': False})
# Create a new user
user, created = User.objects.get_or_create(
email=email,
defaults={
'first_name': full_name.split()[0],
'last_name': full_name.split()[1],
'is_active': True,
},
)
if created:
user.save()
# Generate a JWT token for the new user
token_data = generate_token_and_user_data(user)
return Response(token_data, status=status.HTTP_200_OK)
except (KeyError, ExpiredSignatureError, InvalidTokenError) as e:
return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

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)

87
module_activity/forms.py Normal file
View File

@@ -0,0 +1,87 @@
from django import forms
from module_project import constants
from .models import Intolerance, Symptoms, PastTreatment, ChronicCondition
from module_iam.models import IAmPrincipal
class IntoleranceForm(forms.ModelForm):
class Meta:
model = Intolerance
fields = ['name', 'duration']
label = {
"name": "intolerance",
"duration": "For how long have you been experiencing this intolerance"
}
def save(self, principal_id, commit=True):
instance = super().save(commit=False)
instance.principal = IAmPrincipal.objects.get(pk=principal_id)
if commit:
instance.save()
return instance
class SymptomsForm(forms.ModelForm):
class Meta:
model = Symptoms
fields = ['name', 'duration']
label = {
"name": "Symptoms",
"duration": "For how long have you been experiencing this intolerance"
}
def save(self, principal_id, commit=True):
instance = super().save(commit=False)
instance.principal = IAmPrincipal.objects.get(pk=principal_id)
if commit:
instance.save()
return instance
class SymptomsForm(forms.ModelForm):
class Meta:
model = Symptoms
fields = ['name', 'duration']
label = {
"name": "Symptoms",
"duration": "For how long have you been experiencing this intolerance"
}
def save(self, principal_id, commit=True):
instance = super().save(commit=False)
instance.principal = IAmPrincipal.objects.get(pk=principal_id)
if commit:
instance.save()
return instance
class PastTreatmentForm(forms.ModelForm):
class Meta:
model = PastTreatment
fields = ['name', 'duration']
label = {
"name": "PastTreatment",
"duration": "Treatment Date"
}
def save(self, principal_id, commit=True):
instance = super().save(commit=False)
instance.principal = IAmPrincipal.objects.get(pk=principal_id)
if commit:
instance.save()
return instance
class ChronicConditionForm(forms.ModelForm):
class Meta:
model = ChronicCondition
fields = ['name', 'duration']
label = {
"name": "Chronic Condition",
"duration": "For how long have you been experiencing this disease"
}
def save(self, principal_id, commit=True):
instance = super().save(commit=False)
instance.principal = IAmPrincipal.objects.get(pk=principal_id)
if commit:
instance.save()
return instance

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.0.2 on 2024-02-29 12:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('module_activity', '0006_mealrecord_meal_type'),
]
operations = [
migrations.AlterField(
model_name='principalhealthdata',
name='height',
field=models.DecimalField(blank=True, decimal_places=2, default=0.0, help_text='Enter your height in centimeters.', max_digits=6, null=True, verbose_name='Height (cm)'),
),
migrations.AlterField(
model_name='principalhealthdata',
name='weight',
field=models.DecimalField(blank=True, decimal_places=2, default=0.0, help_text='Enter your weight in kilograms.', max_digits=5, null=True, verbose_name='Weight (kg)'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-03-01 07:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('module_activity', '0007_alter_principalhealthdata_height_and_more'),
]
operations = [
migrations.AddField(
model_name='bowel',
name='stool_name',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

View File

@@ -0,0 +1,141 @@
# Generated by Django 5.0.2 on 2024-03-03 10:16
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('module_activity', '0008_bowel_stool_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='bowel',
name='active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='bowel',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='bowel',
name='created_on',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='bowel',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='bowel',
name='modified_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modified', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='bowel',
name='modified_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='mealrecord',
name='active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='mealrecord',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='mealrecord',
name='created_on',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='mealrecord',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='mealrecord',
name='modified_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modified', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='mealrecord',
name='modified_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='mealsymptomrecord',
name='active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='mealsymptomrecord',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='mealsymptomrecord',
name='created_on',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='mealsymptomrecord',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='mealsymptomrecord',
name='modified_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modified', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='mealsymptomrecord',
name='modified_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='medication',
name='active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='medication',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='medication',
name='created_on',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='medication',
name='deleted',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='medication',
name='modified_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modified', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='medication',
name='modified_on',
field=models.DateTimeField(auto_now=True),
),
]

View File

@@ -49,6 +49,7 @@ class PrincipalHealthData(BaseModel):
weight = models.DecimalField(
max_digits=5,
decimal_places=2,
default=0.0,
blank=True,
null=True,
verbose_name="Weight (kg)",
@@ -58,6 +59,7 @@ class PrincipalHealthData(BaseModel):
height = models.DecimalField(
max_digits=6,
decimal_places=2,
default=0.0,
blank=True,
null=True,
verbose_name="Height (cm)",
@@ -171,7 +173,7 @@ class BeverageRecord(models.Model):
class Meta:
db_table = "beverage_record"
class MealRecord(models.Model):
class MealRecord(BaseModel):
principal = models.ForeignKey(
IAmPrincipal, related_name="meal_principal", on_delete=models.CASCADE
)
@@ -217,7 +219,7 @@ class Medicine(models.Model):
def __str__(self):
return f"{self.name} Medicine"
class Medication(models.Model):
class Medication(BaseModel):
principal = models.ForeignKey(
IAmPrincipal, related_name="medication_principal", on_delete=models.CASCADE
)
@@ -240,13 +242,14 @@ class MedicationMedicine(models.Model):
db_table = "medication_medicine"
class Bowel(models.Model):
class Bowel(BaseModel):
principal = models.ForeignKey(
IAmPrincipal, related_name="bowel_principal", on_delete=models.CASCADE
)
date = models.DateField()
time = models.TimeField()
stool_type = models.CharField(max_length=100, blank=True, null=True)
stool_name = models.CharField(max_length=100, blank=True, null=True)
duration = models.DurationField(blank=True, null=True)
completeness_of_evacuation = models.CharField(max_length=100, blank=True, null=True)
urgency = models.CharField(max_length=100, blank=True, null=True)
@@ -272,7 +275,7 @@ class SymptomTypeAfterMeal(models.Model):
class Meta:
db_table = "symptom_type_after_meal"
class MealSymptomRecord(models.Model):
class MealSymptomRecord(BaseModel):
principal = models.ForeignKey(IAmPrincipal, related_name="meal_symptom_principal", on_delete=models.CASCADE)
date = models.DateField()
time = models.TimeField()

View File

@@ -1,5 +1,6 @@
from django.urls import path
from . import views
from django.views.generic import TemplateView
app_name = "module_activity"
@@ -7,20 +8,33 @@ urlpatterns = [
path('intolerance/<int:principal_id>/', views.IntoleranceView.as_view(), name='intolerance'),
path('intolerance/<int:principal_id>/add/', views.CreateOrUpdateIntoleranceView.as_view(), name='intolerance_add'),
path('intolerance/<int:principal_id>/edit/<int:pk>', views.CreateOrUpdateIntoleranceView.as_view(), name='intolerance_edit'),
path('intolerance/list/<int:principal_id>/', views.IntoleranceListJson.as_view(), name='intolerance_list'),
path('intolerance/action/', views.IntoleranceActionView.as_view(), name='intolerance_action'),
path('intolerance/archive/list/<int:principal_id>/', views.IntoleranceArchiveView.as_view(), name='intolerance_archive'),
path('symptoms/<int:principal_id>/', views.SymptomsView.as_view(), name='symptoms'),
path('symptoms/<int:principal_id>/add/', views.CreateOrUpdateSymptomsView.as_view(), name='symptoms_add'),
path('symptoms/<int:principal_id>/edit/<int:pk>', views.CreateOrUpdateSymptomsView.as_view(), name='symptoms_edit'),
path('symptoms/list/<int:principal_id>/', views.SymptomsListJson.as_view(), name='symptoms_list'),
path('symptoms/action/', views.SymptomsActionView.as_view(), name='symptoms_action'),
path('symptoms/archive/list/<int:principal_id>/', views.SymptomsArchiveView.as_view(), name='symptoms_archive'),
path('past_treatment/<int:principal_id>/', views.PastTreatmentView.as_view(), name='past_treatment'),
path('past_treatment/<int:principal_id>/add/', views.CreateOrUpdatePastTreatmentView.as_view(), name='past_treatment_add'),
path('past_treatment/<int:principal_id>/edit/<int:pk>', views.CreateOrUpdatePastTreatmentView.as_view(), name='past_treatment_edit'),
path('past_treatment/list/<int:principal_id>/', views.PastTreatmentListJson.as_view(), name='past_treatment_list'),
path('past_treatment/action/', views.PastTreatmentActionView.as_view(), name='past_treatment_action'),
path('past_treatment/archive/list/<int:principal_id>/', views.PastTreatmentArchiveView.as_view(), name='past_treatment_archive'),
path('chronic_condition/<int:principal_id>/', views.ChronicConditionView.as_view(), name='chronic_condition'),
path('chronic_condition/<int:principal_id>/add/', views.CreateOrUpdateChronicConditionView.as_view(), name='chronic_condition_add'),
path('chronic_condition/<int:principal_id>/edit/<int:pk>', views.CreateOrUpdateChronicConditionView.as_view(), name='chronic_condition_edit'),
path('chronic_condition/list/<int:principal_id>/', views.ChronicConditionListJson.as_view(), name='chronic_condition_list'),
path('chronic_condition/action/', views.ChronicConditionActionView.as_view(), name='chronic_condition_action'),
path('chronic_condition/archive/list/<int:principal_id>/', views.ChronicConditionArchiveView.as_view(), name='chronic_condition_archive'),
path('user_activity/<int:principal_id>/', views.UserActivityRecordView.as_view(), name='activity_list'),
path('meal_detail/<int:pk>/', views.MealDetialView.as_view(), name='meal_detail'),
@@ -28,4 +42,10 @@ urlpatterns = [
path('bowel_detail/<int:pk>/', views.BowelDetailView.as_view(), name='bowel_detail'),
path('meal_symptom_detail/<int:pk>/', views.MealSymptomDetailView.as_view(), name='meal_symptom_detail'),
path('daily_report/chart/count/', views.ReportChartView.as_view(), name='chart_data'),
path('report/<int:principal_id>/', views.ReportDataView.as_view(), name='report_data'),
]

View File

@@ -1,23 +1,41 @@
import logging
from datetime import datetime
from collections import defaultdict
from datetime import datetime, timedelta
from django.shortcuts import get_object_or_404, render, redirect
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.views import generic
from django.db.models import Q, Prefetch
from .models import Intolerance, Symptoms, ChronicCondition, PastTreatment, MealRecord, Bowel, MealSymptomRecord, Medication
from django.db.models import Q, Prefetch, Count
from .models import (
Intolerance,
Symptoms,
ChronicCondition,
PastTreatment,
MealRecord,
Bowel,
MealSymptomRecord,
Medication,
)
from .forms import (
IntoleranceForm,
SymptomsForm,
PastTreatmentForm,
ChronicConditionForm,
)
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_iam.models import IAmPrincipal
from module_project import constants
from module_iam import iam_constant
from module_project import constants, date_utils
from module_project.utils import JsonResponseUtil
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class BaseView(generic.TemplateView):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_USER
resource = None
action = None
template_name = None
@@ -27,115 +45,236 @@ class BaseView(generic.TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["principal_id"] = self.kwargs.get('principal_id')
context["principal_id"] = self.kwargs.get("principal_id")
return context
class BaseCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = 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"]
def get_initial_queryset(self):
principal_id = self.kwargs.get('principal_id')
deleted_flag = self.request.GET.get('deleted_flag', None)
principal_id = self.kwargs.get("principal_id")
deleted_flag = self.request.GET.get("deleted_flag", None)
if deleted_flag == 'true':
# Show only deleted records
return self.model.objects.filter(principal=principal_id, deleted=True)
else:
# Show all records except deleted ones
return self.model.objects.filter(principal=principal_id, deleted=False)
return self.model.objects.filter(principal=principal_id, deleted=deleted_flag)
def filter_queryset(self, qs):
search_value = self.request.GET.get("search[value]", None)
if search_value:
qs = qs.filter(
Q(name__icontains=search_value) |
Q(duration__icontains=search_value)
Q(name__icontains=search_value) | Q(duration__icontains=search_value)
)
return qs
class BaseActionView(generic.View):
model = Intolerance
model = None
def post(self, request, *args, **kwargs):
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')
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':
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':
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':
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.'
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 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 UserActivityRecordView(generic.View):
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"),
}
def get(self, request, *args, **kwargs):
try:
principal_id = self.kwargs.get('principal_id')
principal_id = self.kwargs.get("principal_id")
date = request.GET.get("date")
print(f"principal_id is {principal_id} data is {date} and type is {type(date)}")
print(
f"principal_id is {principal_id} data is {date} and type is {type(date)}"
)
if not date:
return JsonResponseUtil.error(message="Date parameter is missing")
@@ -145,10 +284,16 @@ class UserActivityRecordView(generic.View):
return JsonResponseUtil.error(message="Invalid date format")
# Retrieve data from different models
meal_records = MealRecord.objects.filter(principal=principal_id, date=date_obj)
medication_records = Medication.objects.filter(principal=principal_id, date=date_obj)
meal_records = MealRecord.objects.filter(
principal=principal_id, date=date_obj
)
medication_records = Medication.objects.filter(
principal=principal_id, date=date_obj
)
bowel_records = Bowel.objects.filter(principal=principal_id, date=date_obj)
meal_symptom_records = MealSymptomRecord.objects.filter(principal=principal_id, date=date_obj)
meal_symptom_records = MealSymptomRecord.objects.filter(
principal=principal_id, date=date_obj
)
print(f"==================meal record {meal_records}")
# Prepare combined results
data = []
@@ -177,71 +322,257 @@ class UserActivityRecordView(generic.View):
except Exception as e:
return JsonResponseUtil.error(message="Something went wrong", errors=str(e))
class MealDetialView(generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_USER
template_name = "module_activity/meal_detail.html"
model = MealRecord
def get_record(self):
id = self.kwargs.get('pk')
id = self.kwargs.get("pk")
meal_record = get_object_or_404(
self.model.objects.prefetch_related(
'food_records', 'beverage_records', 'food_ingredient_records'
"food_records", "beverage_records", "food_ingredient_records"
),
id=id
id=id,
)
return meal_record
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['obj'] = self.get_record()
context["obj"] = self.get_record()
context["page_name"] = self.page_name
return context
class MedicationDetailView(generic.TemplateView):
page_name = 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
)
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["obj"] = self.get_record()
context["page_name"] = self.page_name
return context
class BowelDetailView(generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_USER
template_name = "module_activity/bowel_detail.html"
model = Bowel
def get_record(self):
id = self.kwargs.get('pk')
id = self.kwargs.get("pk")
obj = get_object_or_404(self.model, id=id)
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['obj'] = self.get_record()
context["obj"] = self.get_record()
context["page_name"] = self.page_name
return context
class MealSymptomDetailView(generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_USER
template_name = "module_activity/meal_symptom_details.html"
model = MealSymptomRecord
def get_record(self):
pk = self.kwargs.get('pk')
pk = self.kwargs.get("pk")
obj = get_object_or_404(
MealSymptomRecord.objects.prefetch_related('symptoms_before_meal', 'symptoms_after_meal'),
id=pk
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()
return context
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):
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
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
# 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=204
)
# 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)

View File

@@ -3,6 +3,7 @@ from rest_framework import serializers
from module_iam.models import IAmPrincipal
from module_project import constants
from django.contrib.auth import authenticate
from rest_framework.validators import UniqueValidator
# class BasePasswordSerializer(serializers.Serializer):
# confirm_password = serializers.CharField(write_only=True, required=True)
@@ -22,6 +23,10 @@ from django.contrib.auth import authenticate
# return instance
class RegistrationSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=IAmPrincipal.objects.all(), message="This email address is already in use.")]
)
password = serializers.CharField(write_only=True, required=True)
confirm_password = serializers.CharField(write_only=True, required=True)

View File

@@ -12,5 +12,11 @@ urlpatterns = [
path("verify-otp/", views.OTPVerificationView.as_view()),
path("forget-password/", views.ForgetPasswordView.as_view()),
# path("profile/", views.Profile)
path("account/deactivate/", views.AccountDeactivateView.as_view()),
path('google-signin/', views.GoogleSignin.as_view(), name='google_signin'),
path('apple-signin/', views.AppleSignin.as_view(), name='apple_signin'),
path('version-check/', views.VersionCheck.as_view(), name='version_check'),
]

View File

@@ -4,6 +4,7 @@ from module_project.utils import ApiResponse
from module_iam.models import IAmPrincipal, IAmPrincipalOtp
from rest_framework_simplejwt.tokens import RefreshToken
from django.core.exceptions import ValidationError
import requests
import logging
logger = logging.getLogger(__name__)
@@ -23,12 +24,21 @@ def generate_token_and_user_data(principal):
data = {
"access": str(refresh.access_token),
"refresh": str(refresh),
"first_name": principal.first_name,
"phone_no": str(principal.phone_no),
"complete": principal.register_complete,
}
return data
class GoogleAuthService():
@staticmethod
def get_user_info(access_token):
headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get(
'https://www.googleapis.com/oauth2/v3/userinfo',
headers=headers,
)
user_info = response.json()
return user_info
class AuthService:
"""
Provides authentication services for IAmPrincipal users.

View File

@@ -1,4 +1,4 @@
import datetime
from datetime import datetime
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
@@ -6,14 +6,23 @@ from rest_framework_simplejwt.authentication import JWTAuthentication
from module_project import constants
from module_project.service import SMSService, EmailService
from module_project.utils import ApiResponse
from .utils import AuthService
from module_iam.models import IAmPrincipal, IAmPrincipalOtp
from .serializers import RegistrationSerializer, LoginSerializer, OtpVerificationSerializer, PasswordResetSerializer
from .utils import AuthService, GoogleAuthService
from django.contrib.auth import authenticate
import requests
from module_iam.models import AppVersion, IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType, IAmPrincipalSource
from .serializers import (
RegistrationSerializer,
LoginSerializer,
OtpVerificationSerializer,
PasswordResetSerializer,
)
from django.conf import settings
from rest_framework.response import Response
from .utils import (
generate_token_and_user_data, get_principal_by_email, authticate_with_otp_and_passsword
generate_token_and_user_data,
get_principal_by_email,
authticate_with_otp_and_passsword,
)
@@ -36,14 +45,19 @@ class RegistrationView(APIView):
try:
instance = serializer.save()
principal = instance
token_data = generate_token_and_user_data(principal)
instance.last_login = datetime.now()
instance.principal_type = IAmPrincipalType.get_principal_user()
instance.principal_source = IAmPrincipalSource.get_principal_app()
instance.save()
token_data = generate_token_and_user_data(instance)
except Exception as e:
return ApiResponse.error(
status=status.HTTP_403_FORBIDDEN, message=str(e), errors=str(e)
)
return ApiResponse.success(message=constants.REGISTRATION_SUCCESS, data=token_data)
return ApiResponse.success(
message=constants.REGISTRATION_SUCCESS, data=token_data
)
class LoginView(APIView):
@@ -81,32 +95,9 @@ class LoginView(APIView):
print("Errror reponse")
return validation_result # Return the error response if validation fails
# auth_service = AuthService(principal_model=IAmPrincipal)
# try:
# principal = self.model.objects.get(email=email)
# except Exception as e:
# error_response = {
# "status": status.HTTP_403_FORBIDDEN,
# "message": constants.INVALID_EMAIL_PASSWORD,
# "errors": constants.INVALID_EMAIL_PASSWORD,
# }
# return ApiResponse.error(**error_response)
# try:
# auth_service.authenticate(principal_id=principal.id, password=password)
# except Exception as e:
# error_response = {
# "status": status.HTTP_403_FORBIDDEN,
# "message": e,
# "errors": e,
# }
# return ApiResponse.error(**error_response)
try:
principal.player_id = player_id
principal.last_login = datetime.datetime.now()
principal.last_login = datetime.now()
principal.save()
except Exception as e:
error_response = {
@@ -126,7 +117,9 @@ class OtpRequestView(APIView):
def post(self, request):
if "email" not in request.data:
return ApiResponse.error(message=constants.EMAIL_REQUIRED, errors=constants.EMAIL_REQUIRED)
return ApiResponse.error(
message=constants.EMAIL_REQUIRED, errors=constants.EMAIL_REQUIRED
)
print(f"email auth username: {settings.EMAIL_HOST_USER}")
email = request.data.get("email")
@@ -139,7 +132,9 @@ class OtpRequestView(APIView):
# auth_service = AuthService(IAmPrincipal)
# principal = auth_service.get_principal_by_email(request.data.get("email"))
otp_code = SMSService().create_otp(principal=principal, otp_purpose="Forget password")
otp_code = SMSService().create_otp(
principal=principal, otp_purpose="Forget password"
)
except Exception as e:
return ApiResponse.error(message=str(e), errors=str(e))
@@ -147,18 +142,23 @@ class OtpRequestView(APIView):
email_service = EmailService(
subject="Forget Password",
to=principal.email,
from_email=settings.EMAIL_HOST_USER
from_email=settings.EMAIL_HOST_USER,
)
# Send the email
try:
email_service.load_template("module_auth/email_template.html", context={"code": otp_code} )
email_service.load_template(
"module_auth/email_template.html", context={"code": otp_code, "name": principal.first_name}
)
email_service.send()
except Exception as e:
return ApiResponse.error(message=f"Error sending email: {str(e)}", errors=str(e))
return ApiResponse.error(
message=f"Error sending email: {str(e)}", errors=str(e)
)
return ApiResponse.success(message=constants.SUCCESS)
class OTPVerificationView(APIView):
authentication_classes = []
permission_classes = []
@@ -173,7 +173,7 @@ class OTPVerificationView(APIView):
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
otp = serializer.validated_data.get("otp")
@@ -181,18 +181,16 @@ class OTPVerificationView(APIView):
if isinstance(principal, Response):
return principal
validation_result = authticate_with_otp_and_passsword(
principal, otp=otp
)
validation_result = authticate_with_otp_and_passsword(principal, otp=otp)
print("pasword instance ", validation_result)
if isinstance(validation_result, Response):
print("Errror reponse")
return validation_result # Return the error response if validation fails
token_data = generate_token_and_user_data(principal)
return ApiResponse.success(message=constants.SUCCESS, data=token_data)
return ApiResponse.success(message=constants.SUCCESS)
class ForgetPasswordView(APIView):
authentication_classes = [JWTAuthentication]
@@ -200,6 +198,18 @@ class ForgetPasswordView(APIView):
serializer_class = PasswordResetSerializer
def post(self, request):
email = request.data.get("email")
principal = get_principal_by_email(email=email)
otp_instance = IAmPrincipalOtp.objects.filter(principal=principal).last()
if not otp_instance:
return ApiResponse.error(message=constants.SESSION_EXPIRED)
if otp_instance.is_expired():
return ApiResponse.error(message=constants.SESSION_EXPIRED)
serializer = self.serializer_class(request.user, data=request.data)
if not serializer.is_valid():
error_response = {
@@ -214,4 +224,142 @@ class ForgetPasswordView(APIView):
except Exception as e:
return ApiResponse.error(message=str(e), errors=str(e))
return ApiResponse.success(message=constants.SUCCESS)
return ApiResponse.success(message=constants.SUCCESS)
class AccountDeactivateView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def delete(self, request):
try:
user = IAmPrincipal.objects.get(id=request.user.id)
user.is_active = False
user.deleted = True
user.save()
except Exception as e:
return ApiResponse.error(message=constants.INTERNAL_SERVER_ERROR, errors=str(e))
return ApiResponse.success(message=constants.ACCOUNT_DEACTIVATED)
class GoogleSignin(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
try:
access_token = request.data["access_token"]
user_info = GoogleAuthService.get_user_info(access_token)
print(f"User Info : {user_info}")
# Authenticate user with the email provided by Google
user = IAmPrincipal.objects.filter(email=user_info['email']).first(
) or authenticate(email=user_info['email'], password=None)
if user is None:
# Create a new user if not found
user = IAmPrincipal.objects.create_user(
username=user_info['email'],
email=user_info['email'],
first_name=f"{user_info['given_name']} {user_info['family_name']}",
last_login=datetime.now(),
principal_type=IAmPrincipalType.get_principal_user(),
principal_source=IAmPrincipalSource.get_principal_google()
)
user.save()
token_data = generate_token_and_user_data(user)
# return Response({"token": token.key}, status=status.HTTP_200_OK)
return ApiResponse.success(
message=constants.SUCCESS, data=token_data
)
except Exception as e:
return ApiResponse.error(message=constants.FAILURE, errors=str(e))
import jwt
class AppleSignin(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
try:
authorization_code = request.data['authorization_code']
headers = {
'Authorization': f"Bearer {settings.SOCIAL_AUTH_APPLE_CLIENT_SECRET}"
}
response = requests.post(
'https://appleid.apple.com/auth/token',
data={
'client_id': settings.SOCIAL_AUTH_APPLE_CLIENT_ID,
'code': authorization_code,
'grant_type': 'authorization_code',
'redirect_uri': False,
},
headers=headers,
)
response_data = response.json()
id_token = response_data.get('id_token')
decoded = jwt.decode(
id_token,
'',
algorithms=['ES256'],
options={
'verify_aud': False,
'verify_exp': False,
'verify_iat': False,
},
)
email = decoded.get('email')
full_name = f"{decoded.get('given_name')} {decoded.get('family_name')}"
if IAmPrincipal.objects.filter(email=email).exists():
user = IAmPrincipal.objects.get(email=email)
else:
user = IAmPrincipal.objects.create_user(
username=email,
email=email,
first_name=full_name,
)
user.save()
token_data = generate_token_and_user_data(user)
return ApiResponse.success(
message=constants.SUCCESS, data=token_data
)
except Exception as e:
return ApiResponse.error(message=constants.FAILURE, errors=str(e))
class VersionCheck(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, *args, **kwargs):
app_version = request.GET.get('appVersion')
# Query the database to retrieve the upgrade flags based on the app version
try:
version = AppVersion.objects.get(version=app_version)
except AppVersion.DoesNotExist:
version = None
if version:
upgrade_flags = {
'forceUpgrade': version.force_upgrade,
'recommendUpgrade': version.recommend_upgrade,
}
else:
upgrade_flags = {
'forceUpgrade': False,
'recommendUpgrade': False,
}
return ApiResponse.success(message=constants.SUCCESS, data=upgrade_flags)

View File

@@ -1,6 +1,7 @@
from django import forms
from django.core import validators
from module_project import constants
from module_iam.models import IAmPrincipal
class LoginForm(forms.Form):
email = forms.EmailField(
@@ -12,4 +13,57 @@ class LoginForm(forms.Form):
label="Password",
strip=False,
widget=forms.PasswordInput()
)
)
class UserForm(forms.ModelForm):
password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"}),
validators=[
validators.MinLengthValidator(
limit_value=6, message="Password must be at least 6 characters long. "
)
],
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
class Meta:
model = IAmPrincipal
fields = [
"first_name",
"email",
"password",
"confirm_password",
]
labels = {
"first_name": "Name",
}
def clean_email(self):
email = self.cleaned_data.get('email')
if IAmPrincipal.objects.filter(email=email).exists():
raise forms.ValidationError("This email address is already in use.")
return email
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password and confirm_password and password != confirm_password:
self.add_error("confirm_password", "Passwords do not match.")
return cleaned_data
def save(self, commit=True):
instance = super().save(commit=False)
# Check if it's a new object (create action) or an existing one (update action)
if not instance.pk: # pk is None for new objects
instance.username = self.cleaned_data["email"]
instance.set_password(self.cleaned_data["password"])
if commit:
instance.save()
return instance

View File

@@ -1,5 +1,6 @@
from django.urls import path
from . import views
from django.views.generic import TemplateView
app_name = "module_auth"
@@ -11,7 +12,12 @@ urlpatterns = [
path('password-reset-confirm/<uidb64>/<token>/', views.CustomPasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('password-reset-complete/', views.CustomPasswordResetCompleteView.as_view(), name='password_reset_complete'),
path('users/', views.UserDashView.as_view(), name='users'),
path('users/add/', views.UserCreateOrUpdateView.as_view(), name='user_add'),
path('users/edit/<int:pk>/', views.UserCreateOrUpdateView.as_view(), name='user_edit'),
path('users/list/', views.UserListJson.as_view(), name='users_list'),
path('users/action/', views.UserActionView.as_view(), name='users_action'),
path('user/view/<int:id>/', views.UserRecordView.as_view(), name='user_view'),
path('user/archive/list/', views.UserArchiveList.as_view(), name='user_archive'),
path('user/count/', views.UsersCountView.as_view(), name="user_count")
]

View File

@@ -1,5 +1,6 @@
import logging
from datetime import datetime
from django.db.models import Q, Prefetch
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
@@ -16,10 +17,13 @@ from django.contrib.auth.views import (
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views import generic
from .forms import LoginForm
from module_iam.models import IAmPrincipal
from .forms import LoginForm, UserForm
from module_iam.models import IAmPrincipal, IAmPrincipalType
from module_iam import iam_constant
from module_activity.models import PrincipalHealthData, Intolerance, Symptoms, PastTreatment, ChronicCondition
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_project.mixins import ActionMixin
from module_project.utils import JsonResponseUtil
from module_project import constants
@@ -74,7 +78,7 @@ class CustomPasswordResetDoneView(PasswordResetDoneView):
class UserDashView(LoginRequiredMixin, generic.TemplateView):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_USER
resource = None
action = None
template_name = "module_auth/users_list.html"
@@ -86,20 +90,76 @@ class UserDashView(LoginRequiredMixin, generic.TemplateView):
context["page_name"] = self.page_name
return context
class UserCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = iam_constant.RESOURCE_MANAGE_USER
model = IAmPrincipal
form_class = UserForm
template_name = "module_auth/user_add.html"
success_url = reverse_lazy("module_auth:users")
success_message = "Saved Successfully"
error_message = "An error occurred while saving the data."
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,
"operation": "Edit" if self.object else "Add",
}
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)
# @transaction.atomic
def post(self, request, *args, **kwargs):
print(request.POST)
self.object = self.get_object()
form = self.form_class(request.POST, instance=self.object)
try:
if form.is_valid():
principal = form.save(commit=False)
# Check if it's a new object (create action) or an existing one (update action)
if not principal.pk: # pk is None for new objects
principal.created_by = request.user
principal.principal_type = IAmPrincipalType.objects.filter(name=iam_constant.PRINCIPAL_TYPE_USER).first()
principal.modified_by = request.user
principal.modified_on = datetime.now()
# Save the object
principal.save()
messages.success(request, "Form submitted successfully")
return redirect(self.success_url)
except Exception as e:
self.error_message = constants.ERROR_OCCURR.format(str(e))
print(self.error_message)
messages.error(request, self.error_message)
context = self.get_context_data(form=form)
return render(request, template_name=self.template_name, context=context)
class UserListJson(BaseDatatableView):
model = IAmPrincipal
columns = ["id", "first_name", "email", "phone_no", "date_of_birth", "is_active"]
order_columns = ["id", "first_name", "email", "phone_no", "date_of_birth", "is_active"]
def get_initial_queryset(self):
deleted_flag = self.request.GET.get('deleted_flag', False)
return self.model.objects.filter(principal_type=IAmPrincipalType.get_principal_user(), deleted=deleted_flag)
def filter_queryset(self, qs):
print(f"request is {self.request.GET}")
search_value = self.request.GET.get("search[value]", None)
if search_value:
# print(f"isdiget {search_value.isdigit()}")
# if search_value.isdigit():
# qs = qs.filter(Q(id=search_value))
qs = qs.filter(
Q(id__icontains=search_value)
| Q(first_name__icontains=search_value)
@@ -115,9 +175,34 @@ class UserListJson(BaseDatatableView):
return qs
class UserActionView(ActionMixin):
model = IAmPrincipal
def post(self, request, *args, **kwargs):
action = request.POST.get('action') # 'archive', 'active', or 'unarchive'
ids = request.POST.getlist('ids[]') # List of 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, is_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(is_active=active.capitalize())
message = 'Record updated 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 UserRecordView(LoginRequiredMixin, generic.View):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_USER
resource = None
action = None
model = IAmPrincipal
@@ -160,39 +245,24 @@ class UserRecordView(LoginRequiredMixin, generic.View):
chronic_prefetch
).get(id=id)
print(f"prefetch datatas")
for data in obj.chronic_data:
print(f"data is {data.name, data.duration}")
# Render the template with the principal instance and related data
return render(request, self.template_name, {'obj': obj})
return render(request, self.template_name, {'obj': obj, 'page_name': self.page_name})
class UserArchiveList(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_USER
resource = None
action = None
template_name = "module_auth/users_archive_list.html"
model = IAmPrincipal
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class CustomPasswordResetConfirmView(PasswordResetConfirmView):
template_name = "module_auth/password_reset_confirm.html"
@@ -201,3 +271,23 @@ class CustomPasswordResetConfirmView(PasswordResetConfirmView):
class CustomPasswordResetCompleteView(PasswordResetCompleteView):
template_name = "module_auth/password_reset_complete.html"
class UsersCountView(generic.View):
def get(self, request):
current_year = int(self.request.GET.get("year"))
user_counts = []
# Iterate over each month from January to December
for month in range(1, 13):
# Calculate the start and end dates for the current month
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)
# Query the User model to count users created within the current month
user_count = IAmPrincipal.objects.filter(date_joined__range=(start_date, end_date)).count()
# Append the count to the list
user_counts.append(user_count)
return JsonResponseUtil.success(message=constants.SUCCESS, data=user_counts)

View File

@@ -7,11 +7,6 @@ class FaqSerializer(serializers.ModelSerializer):
model = Faqs
fields = ["id", "question", "answer"]
class FaqListSerializer(serializers.ModelSerializer):
class Meta:
model = Faqs
fields = "__all__"
class OrganizationSerializer(serializers.ModelSerializer):
about_us = serializers.CharField(source='about_us.html', read_only=True)
terms_condition = serializers.CharField(source='terms_condition.html', read_only=True)

View File

@@ -19,8 +19,8 @@ class FaqListAPIView(APIView):
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
class OrganizationAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
authentication_classes = []
permission_classes = []
serializer_class = OrganizationSerializer
model = Organization

View File

@@ -68,16 +68,4 @@ class FaqsForm(forms.ModelForm):
# "faq_category",
"question",
"answer",
"active",
]
# labels = {"faq_category": "Category"}
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
# Fetch the choices for the faq_category field from the database
# self.fields["faq_category"].queryset = FaqCategory.objects.all()
if instance is None:
# This is an add operation, exclude the 'active' field
self.fields.pop("active")

View File

@@ -7,6 +7,8 @@ urlpatterns = [
path('faq/', views.FaqView.as_view(), name="faq"),
path('faq/list/', views.FaqListJson.as_view(), name="faq_list"),
path('faq/add/', views.FaqCreateOrUpdateView.as_view(), name='faq_add'),
path('faq/edit/<int:pk>/', views.FaqCreateOrUpdateView.as_view(), name='faq_edit'),
path('faq/action/', views.FaqActionView.as_view(), name='faq_action'),
path('about-us/', views.AboutUsView.as_view(), name='about_us'),
path('about-us/edit/', views.AboutUsCreateOrUpdateView.as_view(), name='about_us_add'),

View File

@@ -7,11 +7,12 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views import generic
from module_iam.models import IAmPrincipal
from .forms import AboutUsForm, TermsAndConditionForm, FaqCategoryFrom, PrivacyPolicyForm
from module_iam import iam_constant
from .forms import AboutUsForm, TermsAndConditionForm, FaqsForm, PrivacyPolicyForm
from .models import Faqs, Organization
from .api.serializers import FaqListSerializer
from module_project.mixins import DatatablesMixin
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_project.mixins import ActionMixin
from module_project import constants
@@ -19,7 +20,7 @@ logger = logging.getLogger(__name__)
class FaqView(LoginRequiredMixin, generic.TemplateView):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_FAQS
resource = None
action = None
template_name = "module_cms/faq.html"
@@ -32,43 +33,16 @@ class FaqView(LoginRequiredMixin, generic.TemplateView):
return context
# class FaqDatatableView(DatatablesMixin, LoginRequiredMixin, generic.View):
# model = Faqs
# def get_queryset(self):
# return self.model.objects.filter(deleted=False)
# def get(self, request):
# (
# draw,
# start,
# length,
# order_columns,
# order_directions,
# search_value,
# ) = self.get_datatables_params(request)
# queryset = self.get_queryset()
# page_obj, total_count, filtered_count = self.get_pagination(
# queryset, start, length
# )
# serializer = FaqListSerializer(
# page_obj.object_list, many=True
# )
# response = self.prepare_datatables_response(
# draw, total_count, filtered_count, serializer.data
# )
# return response
class FaqListJson(BaseDatatableView):
model = Faqs
columns = ["id", "question", "answer", "active", "deleted"]
order_columns = ["id", "question", "answer", "active", "deleted"]
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}")
@@ -88,14 +62,75 @@ class FaqListJson(BaseDatatableView):
return qs
class FaqCreateOrUpdateView(generic.View):
pass
class FaqCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = iam_constant.RESOURCE_MANAGE_FAQS
resource = iam_constant.RESOURCE_MANAGE_FAQS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = iam_constant.ACTION_CREATE # Default action
template_name = "module_cms/faq_add.html"
model = Faqs
form_class = FaqsForm
success_url = reverse_lazy("module_cms:faq")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"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()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = iam_constant.ACTION_UPDATE
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):
print("Request data: ", request.POST)
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = iam_constant.ACTION_UPDATE
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()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class FaqActionView(ActionMixin):
model = Faqs
class AboutUsView(LoginRequiredMixin, generic.DetailView):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_CMS
template_name = "module_cms/about_us_view.html"
model = Organization
context_object_name = "organization"
@@ -111,7 +146,7 @@ class AboutUsView(LoginRequiredMixin, generic.DetailView):
class AboutUsCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_CMS
resource = None
# Initialize the action as ACTION_CREATE (can change based on logic)
@@ -173,7 +208,7 @@ class AboutUsCreateOrUpdateView(LoginRequiredMixin, generic.View):
class TermsConditionView(LoginRequiredMixin, generic.DetailView):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_T_C
resource = None
action = None
template_name = "module_cms/terms_and_condition_view.html"
@@ -191,7 +226,7 @@ class TermsConditionView(LoginRequiredMixin, generic.DetailView):
class TermsConditionCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_T_C
resource = None
# Initialize the action as ACTION_CREATE (can change based on logic)
@@ -253,7 +288,7 @@ class TermsConditionCreateOrUpdateView(LoginRequiredMixin, generic.View):
class PrivacyPolicyView(LoginRequiredMixin, generic.DetailView):
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_PRIVACYPOLICY
resource = None
action = None
template_name = "module_cms/privacy_policy_view.html"
@@ -271,7 +306,7 @@ class PrivacyPolicyView(LoginRequiredMixin, generic.DetailView):
class PrivacyPolicyCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = None
page_name = iam_constant.RESOURCE_MANAGE_PRIVACYPOLICY
resource = None
# Initialize the action as ACTION_CREATE (can change based on logic)

View File

@@ -0,0 +1,46 @@
[
{
"model": "module_iam.iamappaction",
"pk": 1,
"fields": {
"name": "create",
"label": "create",
"slug": "create",
"created_on": "2024-03-10T01:39:43.656133",
"modified_on": "2024-03-10T01:39:43.656133"
}
},
{
"model": "module_iam.iamappaction",
"pk": 2,
"fields": {
"name": "read",
"label": "read",
"slug": "read",
"created_on": "2024-03-10T01:39:43.656133",
"modified_on": "2024-03-10T01:39:43.656133"
}
},
{
"model": "module_iam.iamappaction",
"pk": 3,
"fields": {
"name": "update",
"label": "update",
"slug": "update",
"created_on": "2024-03-10T01:39:43.656133",
"modified_on": "2024-03-10T01:39:43.656133"
}
},
{
"model": "module_iam.iamappaction",
"pk": 4,
"fields": {
"name": "delete",
"label": "delete",
"slug": "delete",
"created_on": "2024-03-10T01:39:43.656133",
"modified_on": "2024-03-10T01:39:43.656133"
}
}
]

View File

@@ -0,0 +1,46 @@
[
{
"model": "module_iam.iamprincipalsource",
"pk": 1,
"fields": {
"name": "app",
"label": "app",
"slug": "app",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
},
{
"model": "module_iam.iamprincipalsource",
"pk": 2,
"fields": {
"name": "web",
"label": "web",
"slug": "web",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
},
{
"model": "module_iam.iamprincipalsource",
"pk": 3,
"fields": {
"name": "google",
"label": "google",
"slug": "google",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
},
{
"model": "module_iam.iamprincipalsource",
"pk": 4,
"fields": {
"name": "apple",
"label": "apple",
"slug": "apple",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
}
]

View File

@@ -0,0 +1,35 @@
[
{
"model": "module_iam.iamprincipaltype",
"pk": 1,
"fields": {
"name": "admin",
"label": "admin",
"slug": "admin",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
},
{
"model": "module_iam.iamprincipaltype",
"pk": 2,
"fields": {
"name": "subadmin",
"label": "subadmin",
"slug": "subadmin",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
},
{
"model": "module_iam.iamprincipaltype",
"pk": 3,
"fields": {
"name": "user",
"label": "user",
"slug": "user",
"created_on": "2024-03-10T01:39:43.648496",
"modified_on": "2024-03-10T01:39:43.648496"
}
}
]

View File

@@ -0,0 +1,172 @@
[
{
"model": "module_iam.iamappresource",
"pk": 1,
"fields": {
"name": "manage_dashboard",
"label": "manage_dashboard",
"slug": "manage_dashboard",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 2,
"fields": {
"name": "manage_iam",
"label": "manage_iam",
"slug": "manage_iam",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 3,
"fields": {
"name": "manage_user",
"label": "manage_user",
"slug": "manage_user",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 4,
"fields": {
"name": "manage_support",
"label": "manage_support",
"slug": "manage_support",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 5,
"fields": {
"name": "manage_contact_us",
"label": "manage_contact_us",
"slug": "manage_contact_us",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 6,
"fields": {
"name": "manage_feedback",
"label": "manage_feedback",
"slug": "manage_feedback",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 7,
"fields": {
"name": "manage_cms",
"label": "manage_cms",
"slug": "manage_cms",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 8,
"fields": {
"name": "manage_faqs",
"label": "manage_faqs",
"slug": "manage_faqs",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 9,
"fields": {
"name": "manage_tc",
"label": "manage_tc",
"slug": "manage_tc",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
},
{
"model": "module_iam.iamappresource",
"pk": 10,
"fields": {
"name": "manage_privacypolicy",
"label": "manage_privacypolicy",
"slug": "manage_privacypolicy",
"created_on": "2024-03-10T01:39:43.657388",
"modified_on": "2024-03-10T01:39:43.657388",
"action": [
1,
2,
3,
4
]
}
}
]

325
module_iam/forms.py Normal file
View File

@@ -0,0 +1,325 @@
from typing import Any
from django import forms
from django.core.exceptions import ValidationError
from django.core import validators
from django.utils.translation import gettext_lazy as _
from module_project import constants
from . import models
# from .backend import EmailBackend
# from phonenumber_field.formfields import PhoneNumberField
from .iam_constant import PRINCIPAL_TYPE_ADMIN, PRINCIPAL_TYPE_SUBADMIN
from django.contrib.auth import authenticate
class CustomAuthenticationForm(forms.Form):
email = forms.EmailField(
max_length=254,
widget=forms.TextInput(attrs={"autofocus": True}),
label=_("Email"),
)
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "current-password"}),
)
def clean(self):
email = self.cleaned_data.get("email")
password = self.cleaned_data.get("password")
self.user = None
if email and password:
user = authenticate(email=email, password=password)
if user is None:
raise ValidationError({"__all__": [constants.INVALID_EMAIL_PASSWORD]})
elif not user.is_active:
raise ValidationError({"__all__": [constants.ACCOUNT_DEACTIVATED]})
self.user = user
return self.cleaned_data
class IAmPrincipalForm(forms.ModelForm):
password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"}),
validators=[
validators.MinLengthValidator(
limit_value=6, message="Password must be at least 6 characters long. "
)
],
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
is_active = forms.BooleanField(
label="Active",
initial=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
required=False,
)
class Meta:
model = models.IAmPrincipal
fields = [
"principal_type",
"first_name",
"last_name",
"email",
"password",
"confirm_password",
"is_active",
]
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
self.fields["principal_type"].queryset = models.IAmPrincipalType.objects.filter(
active=True, deleted=False
)
# If it's a create action, exclude 'is_active' field
if instance is None:
self.fields.pop("is_active", None)
else:
# Exclude 'password' and 'confirm_password' fields for updates
self.fields.pop("password", None)
self.fields.pop("confirm_password", None)
# Make the 'email' field read-only
self.fields["email"].widget.attrs["readonly"] = True
def clean_email(self):
email = self.cleaned_data.get("email")
# Skip uniqueness validation if it's an update action (instance exists)
if self.instance and self.instance.email == email:
return email
if models.IAmPrincipal.objects.filter(email=email).exists():
raise forms.ValidationError(constants.EMAIL_EXISTS)
return email
def save(self, commit=True):
instance = super().save(commit=False)
# Check if it's a new object (create action) or an existing one (update action)
if not instance.pk: # pk is None for new objects
instance.username = self.cleaned_data["email"]
instance.set_password(self.cleaned_data["password"])
principal_type = self.cleaned_data.get("principal_type")
if principal_type is not None:
# Set is_superuser and is_staff based on principal_type
if principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_ADMIN):
instance.is_superuser = True
elif principal_type == models.IAmPrincipalType.objects.get(name=PRINCIPAL_TYPE_SUBADMIN):
instance.is_staff = True
if commit:
instance.save()
return instance
class IAmPrincipalProfileForm(forms.ModelForm):
GENDER_CHOICES = (
("male", "Male"),
("female", "Female"),
("other", "Other"),
)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
email = forms.EmailField(required=True)
password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={"autocomplete": "off"})
)
# date_of_birth = forms.CharField(widget=forms.DateInput(attrs={'type': 'date'}))
phone_number = forms.CharField(
widget=forms.TextInput(),
)
# is_staff = forms.BooleanField(
# label="Staff Status",
# label_suffix="",
# initial=True,
# required=False,
# help_text="Check this box to designate that this user will be assigned permissions in the future.",
# )
# is_superuser = forms.BooleanField(
# label="SuperAdmin Status",
# label_suffix="",
# required=False,
# help_text="Check this box to designates that this user has all permissions without explicitly assigning them.",
# )
# gender = forms.ChoiceField(choices=GENDER_CHOICES)
class Meta:
model = models.IAmPrincipal
fields = [
"principal_type",
"first_name",
"last_name",
"email",
"password",
"confirm_password",
# 'gender',
# 'date_of_birth',
"phone_number",
# 'address_line1',
# 'address_line2',
# 'city',
# 'state',
# 'country',
# 'post_code',
# 'profile_photo',
# "is_staff",
# "is_superuser",
]
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
self.fields["principal_type"].queryset = models.IAmPrincipalType.objects.filter(
active=True, deleted=False
)
# self.fields['principal_source'].queryset = models.IAmPrincipalSource.objects.filter(active=True, deleted=False)
# Check if an instance is provided and customize the form fields accordingly
if instance is not None:
# Exclude the 'password' and 'confirm_password' fields
self.fields.pop("password", None)
self.fields.pop("confirm_password", None)
# Make the 'email' field read-only
self.fields["email"].widget.attrs["readonly"] = True
# Modify the 'is_superuser' field to be not required
# self.fields["is_superuser"].required = False
# self.fields["is_staff"].required = False
# Add or modify the 'is_active' field
self.fields["is_active"] = forms.BooleanField(
label="Active",
initial=instance.is_active,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
required=False,
)
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password and confirm_password and password != confirm_password:
self.add_error("confirm_password", "Password does not match")
return cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class ProfileEditForm(forms.ModelForm):
gender = forms.ChoiceField(choices=(('Male', 'Male'),('Female', 'Female'),('Other', 'Other')))
profile_photo = forms.ImageField(required=False)
class Meta:
model = models.IAmPrincipal
fields = [
"profile_photo",
"first_name",
"last_name",
"date_of_birth",
"gender",
"phone_no"
]
class IAmPrincipalGroupLinkForm(forms.ModelForm):
class Meta:
model = models.IAmPrincipal
fields = [
# "principal_type",
"email",
"principal_group",
]
# principal_type = forms.ModelChoiceField(
# label="Principal Type",
# queryset=models.IAmPrincipalType.objects.filter(active=True, deleted=False),
# widget=forms.widgets.TextInput(attrs={"readonly": True}),
# )
principal_group = forms.ModelMultipleChoiceField(
label="Groups",
queryset=models.IAmPrincipalGroup.objects.filter(active=True, deleted=False),
required=False,
widget=forms.widgets.SelectMultiple(
attrs={"class": "form_select js-example-basic-multiple"}
),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the 'email' field read-only
# self.fields['principal_type'].widget.attrs['disabled'] = True
self.fields['email'].widget.attrs['readonly'] = True
class IAmPrincipalTypeForm(forms.ModelForm):
class Meta:
model = models.IAmPrincipalType
fields = ["name", "active"]
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
if instance is None:
self.fields.pop("active")
class IAmPrincipalGroupRoleLinkForm(forms.ModelForm):
class Meta:
model = models.IAmPrincipalGroup
fields = ["name", "role", "active"]
role = forms.ModelMultipleChoiceField(
queryset=models.IAmRole.objects.filter(active=True, deleted=False),
required=False,
widget=forms.widgets.SelectMultiple(
attrs={"class": "form-select js-example-basic-multiple"}
),
)
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
# data = kwargs.get('data')
super().__init__(*args, **kwargs)
if instance is None:
# This is an add operation, exclude the 'active' field
self.fields.pop("active")
class IAmPrincipalRoleAppResourceActionLinkForm(forms.ModelForm):
class Meta:
model = models.IAmRole
fields = ["name", "active", "app_resource_action"]
required = {"app_resource_action": False}
app_resource_action = forms.ModelMultipleChoiceField(
queryset=models.IAmAppResourceActionLink.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False,
)
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
if instance is None:
self.fields.pop("active")

View File

@@ -1,25 +1,34 @@
# principal type constant
PRINCIPAL_TYPE_USER = "user"
PRINCIPAL_TYPE_ADMIN = "admin"
PRINCIPAL_TYPE_SUBADMIN = "subadmin"
# principal source constant
PRINCIPAL_SOURCE_APP = "app"
PRINCIPAL_SOURCE_WEB = "web"
PRINCIPAL_SOURCE_GOOGLE = "google"
PRINCIPAL_SOURCE_APPLE = "apple"
# app action constant
ACTION_CREATE = "create"
ACTION_READ = "read"
ACTION_UPDATE = "update"
ACTION_DELETE = "delete"
RESOURCE_MANAGE_DASHBOARD = "manage_dashboard"
RESOURCE_MANAGE_IAM = "manage_iam"
RESOURCE_MANAGE_CUSTOMER = "manage_customer"
RESOURCE_MANAGE_WALLET = "manage_wallet"
RESOURCE_MANAGE_PAYMENT = "manage_payment"
RESOURCE_MANAGE_GAMES = "manage_games"
RESOURCE_MANAGE_USER = "manage_user"
RESOURCE_MANAGE_SUPPORT = "manage_support"
RESOURCE_MANAGE_CONTACT_US = "manage_contact_us"
RESOURCE_MANAGE_TICKET = "manage_ticket"
RESOURCE_MANAGE_CMS = "manage_cms"
RESOURCE_MANAGE_REPORTS = "manage_reports"
RESOURCE_MANAGE_COUPON = "manage_coupon"
RESOURCE_MANAGE_FEEDBACK = "manage_feedback"
RESOURCE_MANAGE_STOCK = "manage_stock"
RESOURCE_MANAGE_NOTIFICATION = "manage_notification"
RESOURCE_MANAGE_CMS = "manage_cms"
RESOURCE_MANAGE_FAQS = "manage_faqs"
RESOURCE_MANAGE_T_C = "manage_tc"
RESOURCE_MANAGE_PRIVACYPOLICY = "manage_privacypolicy"
# These constants are used solely for managing the active and inactive state of pages

View File

@@ -0,0 +1,60 @@
from .iam_constant import (
PRINCIPAL_TYPE_USER,
PRINCIPAL_TYPE_ADMIN,
PRINCIPAL_TYPE_SUBADMIN,
PRINCIPAL_SOURCE_APP,
PRINCIPAL_SOURCE_WEB,
PRINCIPAL_SOURCE_GOOGLE,
PRINCIPAL_SOURCE_APPLE,
ACTION_CREATE,
ACTION_READ,
ACTION_UPDATE,
ACTION_DELETE,
RESOURCE_MANAGE_DASHBOARD,
RESOURCE_MANAGE_IAM,
RESOURCE_MANAGE_USER,
RESOURCE_MANAGE_SUPPORT,
RESOURCE_MANAGE_CONTACT_US,
RESOURCE_MANAGE_FEEDBACK,
RESOURCE_MANAGE_NOTIFICATION,
RESOURCE_MANAGE_CMS,
RESOURCE_MANAGE_FAQS,
RESOURCE_MANAGE_T_C,
RESOURCE_MANAGE_PRIVACYPOLICY,
RESOURCE_IAM_PRINCIPAL,
RESOURCE_IAM_PRINCIPAL_GROUP,
RESOURCE_IAM_GROUP,
RESOURCE_IAM_ROLE,
)
def iam_constants_context(request):
return {
'iam_constants_context': {
'PRINCIPAL_TYPE_USER': PRINCIPAL_TYPE_USER,
'PRINCIPAL_TYPE_ADMIN': PRINCIPAL_TYPE_ADMIN,
'PRINCIPAL_TYPE_SUBADMIN': PRINCIPAL_TYPE_SUBADMIN,
'PRINCIPAL_SOURCE_APP': PRINCIPAL_SOURCE_APP,
'PRINCIPAL_SOURCE_WEB': PRINCIPAL_SOURCE_WEB,
'PRINCIPAL_SOURCE_GOOGLE': PRINCIPAL_SOURCE_GOOGLE,
'PRINCIPAL_SOURCE_APPLE': PRINCIPAL_SOURCE_APPLE,
'ACTION_CREATE': ACTION_CREATE,
'ACTION_READ': ACTION_READ,
'ACTION_UPDATE': ACTION_UPDATE,
'ACTION_DELETE': ACTION_DELETE,
'RESOURCE_MANAGE_DASHBOARD': RESOURCE_MANAGE_DASHBOARD,
'RESOURCE_MANAGE_IAM': RESOURCE_MANAGE_IAM,
'RESOURCE_MANAGE_USER': RESOURCE_MANAGE_USER,
'RESOURCE_MANAGE_SUPPORT': RESOURCE_MANAGE_SUPPORT,
'RESOURCE_MANAGE_CONTACT_US': RESOURCE_MANAGE_CONTACT_US,
'RESOURCE_MANAGE_FEEDBACK': RESOURCE_MANAGE_FEEDBACK,
'RESOURCE_MANAGE_NOTIFICATION': RESOURCE_MANAGE_NOTIFICATION,
'RESOURCE_MANAGE_CMS': RESOURCE_MANAGE_CMS,
'RESOURCE_MANAGE_FAQS': RESOURCE_MANAGE_FAQS,
'RESOURCE_MANAGE_T_C': RESOURCE_MANAGE_T_C,
'RESOURCE_MANAGE_PRIVACYPOLICY': RESOURCE_MANAGE_PRIVACYPOLICY,
'RESOURCE_IAM_PRINCIPAL': RESOURCE_IAM_PRINCIPAL,
'RESOURCE_IAM_PRINCIPAL_GROUP': RESOURCE_IAM_PRINCIPAL_GROUP,
'RESOURCE_IAM_GROUP': RESOURCE_IAM_GROUP,
'RESOURCE_IAM_ROLE': RESOURCE_IAM_ROLE,
}
}

View File

@@ -0,0 +1,169 @@
from datetime import datetime
from .iam_constant import (
PRINCIPAL_TYPE_USER,
PRINCIPAL_TYPE_ADMIN,
PRINCIPAL_TYPE_SUBADMIN,
PRINCIPAL_SOURCE_APP,
PRINCIPAL_SOURCE_WEB,
PRINCIPAL_SOURCE_GOOGLE,
PRINCIPAL_SOURCE_APPLE,
ACTION_CREATE,
ACTION_READ,
ACTION_UPDATE,
ACTION_DELETE,
RESOURCE_MANAGE_DASHBOARD,
RESOURCE_MANAGE_IAM,
RESOURCE_MANAGE_USER,
RESOURCE_MANAGE_CONTACT_US,
RESOURCE_MANAGE_FEEDBACK,
RESOURCE_MANAGE_FAQS,
RESOURCE_MANAGE_T_C,
RESOURCE_MANAGE_CMS,
RESOURCE_MANAGE_PRIVACYPOLICY,
RESOURCE_MANAGE_SUPPORT
)
class IAMPrincipalType:
ADMIN = PRINCIPAL_TYPE_ADMIN
SUBADMIN = PRINCIPAL_TYPE_SUBADMIN
USER = PRINCIPAL_TYPE_USER
categories = [
ADMIN,
SUBADMIN,
USER,
]
@staticmethod
def create_iam_principal_type_fixture_data():
iam_category_fixture_data = []
created_on = datetime.now().isoformat()
modified_on = datetime.now().isoformat()
for idx, category in enumerate(IAMPrincipalType.categories, start=1):
iam_category_fixture_data.append(
{
"model": "module_iam.iamprincipaltype",
"pk": idx,
"fields": {
"name": category,
"label": category,
"slug": category,
"created_on": created_on,
"modified_on": modified_on,
},
}
)
return iam_category_fixture_data
class IAMPrincipalSource:
source = [
PRINCIPAL_SOURCE_APP,
PRINCIPAL_SOURCE_WEB,
PRINCIPAL_SOURCE_GOOGLE,
PRINCIPAL_SOURCE_APPLE
]
@staticmethod
def create_iam_principal_source_fixture_data():
iam_principal_source_fixture_data = []
created_on = datetime.now().isoformat()
modified_on = datetime.now().isoformat()
for idx, principal_source in enumerate(IAMPrincipalSource.source, start=1,):
iam_principal_source_fixture_data.append(
{
"model": "module_iam.iamprincipalsource",
"pk": idx,
"fields": {
"name": principal_source,
"label": principal_source,
"slug": principal_source,
"created_on": created_on,
"modified_on": modified_on,
},
}
)
return iam_principal_source_fixture_data
class IAMActions:
CREATE = ACTION_CREATE
READ = ACTION_READ
UPDATE = ACTION_UPDATE
DELETE = ACTION_DELETE
actions = [
CREATE,
READ,
UPDATE,
DELETE,
]
@staticmethod
def create_iam_action_fixture_data():
iam_action_fixture_data = []
created_on = datetime.now().isoformat()
modified_on = datetime.now().isoformat()
for idx, action in enumerate(IAMActions.actions, start=1):
iam_action_fixture_data.append(
{
"model": "module_iam.iamappaction",
"pk": idx,
"fields": {
"name": action,
"label": action,
"slug": action,
"created_on": created_on,
"modified_on": modified_on,
},
}
)
return iam_action_fixture_data
class IAMResources:
DASHBOARD = RESOURCE_MANAGE_DASHBOARD
IAM = RESOURCE_MANAGE_IAM
USER = RESOURCE_MANAGE_USER
SUPPORT = RESOURCE_MANAGE_SUPPORT
CONTACT_US = RESOURCE_MANAGE_CONTACT_US
FEEDBACK = RESOURCE_MANAGE_FEEDBACK
CMS = RESOURCE_MANAGE_CMS
FAQS = RESOURCE_MANAGE_FAQS
T_C = RESOURCE_MANAGE_T_C
PRIVACYPOLICY = RESOURCE_MANAGE_PRIVACYPOLICY
resources = [
DASHBOARD,
IAM,
USER,
SUPPORT,
CONTACT_US,
FEEDBACK,
CMS,
FAQS,
T_C,
PRIVACYPOLICY,
]
@staticmethod
def create_iam_resource_fixture_data():
iam_resource_fixture_data = []
created_on = datetime.now().isoformat()
modified_on = datetime.now().isoformat()
for idx, resource in enumerate(IAMResources.resources, start=1):
iam_resource_fixture_data.append(
{
"model": "module_iam.iamappresource",
"pk": idx,
"fields": {
"name": resource,
"label": resource,
"slug": resource,
"created_on": created_on,
"modified_on": modified_on,
"action": [1, 2, 3, 4],
},
}
)
return iam_resource_fixture_data

View File

@@ -0,0 +1,122 @@
import os
import json
import subprocess
from datetime import datetime
from tqdm import tqdm
from django.core.management.base import BaseCommand
from module_iam.iam_fixture_script import IAMPrincipalType, IAMActions, IAMResources, IAMPrincipalSource
class Command(BaseCommand):
help = "Load IAM fixtures data"
def handle(self, *args, **options):
app_name = "module_iam"
try:
self.stdout.write(self.style.SUCCESS("IAM fixtures data loading started..."))
# Ensure the fixture directory exists
fixture_directory = os.path.join(app_name, "fixtures")
if not os.path.exists(fixture_directory):
os.makedirs(fixture_directory)
# Generate IAM category fixture data
principal_type_fixture_data = IAMPrincipalType.create_iam_principal_type_fixture_data()
# Specify the app name and fixture filename for category fixtures
categories_fixture_filename = os.path.join(fixture_directory, "iam_principal_type_fixture.json")
principal_type_fixture_data_list = []
with tqdm(total=len(principal_type_fixture_data), desc="Loading IAM principal type fixture") as pbar:
for item in principal_type_fixture_data:
principal_type_fixture_data_list.append(item)
pbar.update(1)
# Dump category fixture data as JSON
with open(categories_fixture_filename, "w") as fixture_file:
json.dump(principal_type_fixture_data, fixture_file, indent=4)
self.stdout.write(
self.style.SUCCESS(f"IAM category fixture data has been loaded successfully. Fixture file location: {categories_fixture_filename}")
)
principal_source_fixture_data = IAMPrincipalSource.create_iam_principal_source_fixture_data()
# Specify the app name and fixture filename for source fixtures
source_fixture_filename = os.path.join(fixture_directory, "iam_principal_source_fixture.json")
principal_source_fixture_data_list = []
with tqdm(total=len(principal_source_fixture_data), desc="Loading IAM principal source fixture") as pbar:
for item in principal_source_fixture_data:
principal_source_fixture_data_list.append(item)
pbar.update(1)
# Dump category fixture data as JSON
with open(source_fixture_filename, "w") as fixture_file:
json.dump(principal_source_fixture_data, fixture_file, indent=4)
self.stdout.write(
self.style.SUCCESS(f"IAM category fixture data has been loaded successfully. Fixture file location: {categories_fixture_filename}")
)
# Generate IAM action fixture data
action_fixture_data = IAMActions.create_iam_action_fixture_data()
# Specify the fixture filename for action fixtures
action_fixture_filename = os.path.join(fixture_directory, "iam_actions_fixture.json")
action_fixture_data_list = []
with tqdm(total=len(action_fixture_data), desc="Loading IAM action fixture") as pbar:
for item in action_fixture_data:
action_fixture_data_list.append(item)
pbar.update(1)
# Dump action fixture data as JSON
with open(action_fixture_filename, "w") as fixture_file:
json.dump(action_fixture_data, fixture_file, indent=4)
self.stdout.write(
self.style.SUCCESS(f"IAM action fixture data has been loaded successfully. Fixture file location: {action_fixture_filename}")
)
# Generate IAM resource fixture data
resource_fixture_data = IAMResources.create_iam_resource_fixture_data()
# Specify the fixture filename for resource fixtures
resource_fixture_filename = os.path.join(fixture_directory, "iam_resources_fixture.json")
resource_fixture_data_list = []
with tqdm(total=len(resource_fixture_data), desc="Loading IAM resource fixture") as pbar:
for item in resource_fixture_data:
resource_fixture_data_list.append(item)
pbar.update(1)
# Dump resource fixture data as JSON
with open(resource_fixture_filename, "w") as fixture_file:
json.dump(resource_fixture_data, fixture_file, indent=4)
self.stdout.write(
self.style.SUCCESS(f"IAM resource fixture data has been loaded successfully. Fixture file location: {resource_fixture_filename}")
)
# Run the loaddata command to load the created fixtures
loaddata_command_categories = f"python manage.py loaddata {categories_fixture_filename}"
subprocess.run(loaddata_command_categories, shell=True)
loaddata_command_categories = f"python manage.py loaddata {source_fixture_filename}"
subprocess.run(loaddata_command_categories, shell=True)
loaddata_command_actions = f"python manage.py loaddata {action_fixture_filename}"
subprocess.run(loaddata_command_actions, shell=True)
loaddata_command_resources = f"python manage.py loaddata {resource_fixture_filename}"
subprocess.run(loaddata_command_resources, shell=True)
self.stdout.write(
self.style.SUCCESS("IAM fixtures data loading completed successfully.")
)
except Exception as e:
# Handle exceptions here
self.stderr.write(
self.style.ERROR(f"IAM fixtures data loading failed: {str(e)}")
)

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.0.2 on 2024-03-11 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('module_iam', '0003_alter_iamprincipal_gender'),
]
operations = [
migrations.CreateModel(
name='AppVersion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('version', models.CharField(max_length=10)),
('force_upgrade', models.BooleanField(default=False, help_text='Indicates whether a force upgrade is needed for this app version.')),
('recommend_upgrade', models.BooleanField(default=False, help_text='Indicates whether a recommend upgrade is needed for this app version.')),
],
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.0.2 on 2024-03-11 07:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('module_iam', '0004_appversion'),
]
operations = [
migrations.AlterModelTable(
name='appversion',
table='app_version',
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.0.2 on 2024-03-11 08:18
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('module_iam', '0005_alter_appversion_table'),
]
operations = [
migrations.AlterField(
model_name='appversion',
name='version',
field=models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\d+\\.\\d+\\.\\d+$')]),
),
]

View File

@@ -2,17 +2,28 @@ from collections.abc import Iterable
import datetime
import random
import string
# from manage_wallets.models import Wallet, Transaction, TransactionStatus, TransactionType
from django.conf import settings
from django.core.cache import cache
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
# from phonenumber_field.modelfields import PhoneNumberField
from module_project.utils import RandomGenerator
from .resource_action import PRINCIPAL_TYPE_USER, PRINCIPAL_TYPE_ADMIN
from .iam_constant import (
PRINCIPAL_TYPE_USER,
PRINCIPAL_TYPE_ADMIN,
PRINCIPAL_TYPE_SUBADMIN,
PRINCIPAL_SOURCE_APP,
PRINCIPAL_SOURCE_APPLE,
PRINCIPAL_SOURCE_GOOGLE,
PRINCIPAL_SOURCE_WEB,
)
# from .utils import UserContext
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
@@ -102,14 +113,60 @@ class IAmPrincipalType(MasterModel):
db_table = "iam_principal_type"
@classmethod
def get_principal_type(cls, type):
return cls.objects.filter(name=type).first()
def get_principal_type(cls, name):
cache_key = f"principal_{name}"
principal = cache.get(cache_key)
if not principal:
principal = cls.objects.filter(name=name).first()
cache.set(cache_key, principal, timeout=60 * 15) # Cache for 15 minutes
return principal
@classmethod
def get_principal_user(cls):
return cls.get_principal_type(PRINCIPAL_TYPE_USER)
@classmethod
def get_principal_admin(cls):
return cls.get_principal_type(PRINCIPAL_TYPE_ADMIN)
@classmethod
def get_principal_subadmin(cls):
return cls.get_principal_type(PRINCIPAL_TYPE_SUBADMIN)
class IAmPrincipalSource(MasterModel):
class Meta:
db_table = "iam_principal_source"
@classmethod
def get_principal_source(cls, name):
cache_key = f"principal_{name}"
principal = cache.get(cache_key)
if not principal:
principal = cls.objects.filter(name=name).first()
cache.set(cache_key, principal, timeout=60 * 15) # Cache for 15 minutes
return principal
@classmethod
def get_principal_web(cls):
return cls.get_principal_source(PRINCIPAL_SOURCE_WEB)
@classmethod
def get_principal_app(cls):
return cls.get_principal_source(PRINCIPAL_SOURCE_APP)
@classmethod
def get_principal_google(cls):
return cls.get_principal_source(PRINCIPAL_SOURCE_GOOGLE)
@classmethod
def get_principal_apple(cls):
return cls.get_principal_source(PRINCIPAL_SOURCE_APPLE)
class IAmAppAction(MasterModel):
class Meta:
@@ -239,7 +296,7 @@ class IAmPrincipalManager(BaseUserManager):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
extra_fields.setdefault("phone_no", "+919978895465")
extra_fields.setdefault("gender", "M")
extra_fields.setdefault("gender", "Male")
extra_fields.setdefault("date_of_birth", timezone.now())
extra_fields.setdefault("created_by", None)
extra_fields.setdefault("created_on", timezone.now())
@@ -298,7 +355,12 @@ class IAmPrincipal(AbstractUser):
related_name="principal_groups",
)
register_complete = models.BooleanField(default=False)
player_id = models.CharField(max_length=255, null=True, blank=True, help_text="OneSignal player id for push notification")
player_id = models.CharField(
max_length=255,
null=True,
blank=True,
help_text="OneSignal player id for push notification",
)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
@@ -367,3 +429,15 @@ class IAmPrincipalBiometric(BaseModel):
def __str__(self):
return f"{self.principal.first_name}:{self.biometric_type}"
class AppVersion(models.Model):
version = models.CharField(max_length=10, validators=[RegexValidator(r'^\d+\.\d+\.\d+$')])
force_upgrade = models.BooleanField(default=False, help_text='Indicates whether a force upgrade is needed for this app version.')
recommend_upgrade = models.BooleanField(default=False, help_text='Indicates whether a recommend upgrade is needed for this app version.')
class Meta:
db_table = "app_version"
def __str__(self):
return self.version

View File

@@ -4,5 +4,32 @@ from . import views
app_name = "module_iam"
urlpatterns = [
path('dashboard/', views.DashboardView.as_view(), name="dashboard")
path('dashboard/', views.DashboardView.as_view(), name="dashboard"),
# path('principal/', views.PrincipalListView.as_view(), name="principal_list"),
# path('principal/add/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_add"),
# path('principal/edit/<int:pk>', views.PrincipalCreateOrUpdateView.as_view(), name="principal_edit"),
# path('principal/delete/<int:pk>', views.PrincipalDeleteView.as_view(), name="principal_delete"),
path('principal/group/link/', views.PrincipalGroupLinkView.as_view(), name="principal_group_link"),
path('principal/group/link/', views.PrincipalGroupLinkAdminListJsonView.as_view(), name="principal_group_link_list"),
# path('principal/group/link/edit/<int:pk>/', views.PrincipalGroupLinkEditView.as_view(), name="principal_group_link_edit"),
path('principal/group/', views.PrincipalGroupView.as_view(), name="principal_group"),
path('principal/group/list', views.PrincipalGroupListJsonView.as_view(), name="principal_group_list"),
path('principal/group/add/', views.PrincipalGroupCreateOrUpdateView.as_view(), name="principal_group_add"),
path('principal/group/edit/<int:pk>/', views.PrincipalGroupCreateOrUpdateView.as_view(), name="principal_group_edit"),
path('principal/group/action//', views.PrincipalGroupActionView.as_view(), name="principal_group_action"),
path('principal/role/', views.AppRoleView.as_view(), name="role"),
path('principal/role/list/', views.AppRoleListJsonView.as_view(), name="role_list"),
path('principal/role/add/', views.AppRoleCreateOrUpdateView.as_view(), name="role_add"),
path('principal/role/edit/<int:pk>/', views.AppRoleCreateOrUpdateView.as_view(), name="role_edit"),
path('principal/role/action/', views.AppRoleActionView.as_view(), name="role_action"),
path("profile/", views.PrincipalProfileView.as_view(), name="profile_details"),
path("profile/edit/", views.PrincipalProfileEditView.as_view(), name="profile_details_edit")
]

View File

@@ -1,7 +1,374 @@
from django.shortcuts import render
from typing import Any
from django.db.models.base import Model as Model
from django.db.models.query import QuerySet
from django.views import generic
import logging
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from module_iam import iam_constant
from module_project.mixins import DatatablesMixin
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_project.mixins import ActionMixin
from .forms import (
CustomAuthenticationForm,
IAmPrincipalForm,
IAmPrincipalGroupRoleLinkForm,
IAmPrincipalRoleAppResourceActionLinkForm,
IAmPrincipalGroupLinkForm,
ProfileEditForm
)
from .models import (
IAmPrincipal,
IAmPrincipalType,
IAmAppResourceActionLink,
IAmPrincipalGroup,
IAmRole,
)
from module_project import constants
logger = logging.getLogger(__name__)
# Create your views here.
class DashboardView(generic.TemplateView):
template_name = "base_structure/layout/dashboard.html"
page_name = iam_constant.RESOURCE_MANAGE_DASHBOARD
template_name = "base_structure/layout/dashboard.html"
def get_user_count(self):
obj = IAmPrincipal.objects.all()
# Count active users
active_user_count = obj.filter(is_active=True).count()
# Count total users
total_user_count = obj.count()
return active_user_count, total_user_count
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
active_user_count, total_user_count = self.get_user_count()
context['active_user_count'] = active_user_count
context['total_user_count'] = total_user_count
context['page_name'] = self.page_name
return context
class PrincipalGroupLinkView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_IAM_PRINCIPAL_GROUP
model = IAmPrincipal
template_name = "module_iam/iam_principal_group_link.html"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class PrincipalGroupLinkAdminListJsonView(BaseDatatableView):
model = IAmPrincipal
columns = ["id", "first_name", "email", "principal_type__name", "is_active"],
order_columns = ["id", "first_name", "email"]
def get_initial_queryset(self):
deleted_flag = self.request.GET.get('deleted_flag', False)
return self.model.objects.filter(deleted=deleted_flag).exclude(principal_type__name=iam_constant.PRINCIPAL_TYPE_USER)
class PrincipalGroupView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_IAM_GROUP
model = IAmPrincipalGroup
template_name = "module_iam/iam_group.html"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
def filter_queryset(self, qs):
search_value = self.request.GET.get("search[value]", None)
if search_value:
qs = qs.filter(
Q(id__icontains=search_value)
| Q(name__icontains=search_value)
)
return qs
class PrincipalGroupListJsonView(BaseDatatableView):
model = IAmPrincipalGroup
columns = ["id", "name", "active"]
order_columns = ["id", "name", "active"]
def get_initial_queryset(self):
deleted_flag = self.request.GET.get('deleted_flag', False)
return self.model.objects.filter(deleted=deleted_flag)
def filter_queryset(self, qs):
search_value = self.request.GET.get("search[value]", None)
if search_value:
qs = qs.filter(
Q(id__icontains=search_value)
| Q(name__icontains=search_value)
)
return qs
def generate_role_data(self, queryset):
roles_data = []
for obj in queryset:
roles = [{'name': role.name} for role in obj.role.all()]
print(f"role data is this {roles}")
roles_data.append({
'id': obj.id,
'name': obj.name,
'active': str(obj.active),
'roles': roles
})
return roles_data
def get_context_data(self, *args, **kwargs):
roles = self.filter_queryset(self.get_initial_queryset())
role_data = self.generate_role_data(roles)
context = super().get_context_data(*args, **kwargs)
context['recordsTotal'] = len(role_data)
context['recordsFiltered'] = len(role_data)
context['data'] = role_data
context['result'] = 'ok'
return context
class PrincipalGroupCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = iam_constant.RESOURCE_IAM_GROUP
page_title = "Principal Group"
model = IAmPrincipalGroup
template_name = "module_iam/iam_group_add.html"
form_class = IAmPrincipalGroupRoleLinkForm
success_url = reverse_lazy("module_iam:principal_group")
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,
"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):
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()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class PrincipalGroupActionView(ActionMixin):
model = IAmPrincipalGroup
class AppRoleView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_IAM_ROLE
model = IAmRole
template_name = "module_iam/iam_role.html"
def get_context_data(self, **kwargs) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class AppRoleListJsonView(BaseDatatableView):
model = IAmRole
columns = ["id", "name", "active", "resources"]
order_columns = ["id", "name"]
def get_initial_queryset(self):
deleted_flag = self.request.GET.get('deleted_flag', False)
return (
super(AppRoleListJsonView, self)
.get_initial_queryset()
.prefetch_related(
"app_resource_action",
"app_resource_action__app_resource",
"app_resource_action__app_action",
)
.filter(deleted=deleted_flag)
)
def filter_queryset(self, qs):
search_value = self.request.GET.get("search[value]", None)
if search_value:
qs = qs.filter(
Q(id__icontains=search_value)
| Q(name__icontains=search_value)
| Q(app_resource_action__app_resource__name__icontains=search_value)
| Q(app_resource_action__app_action__name__icontains=search_value)
)
return qs
def generate_resource_data(self, roles):
role_data = []
for role in roles:
role_info = {
"id": role.id,
"name": role.name,
"active": str(role.active),
"resources": {},
}
for link in role.app_resource_action.all():
resource = link.app_resource.name
action = link.app_action.name
if resource in role_info["resources"]:
role_info["resources"][resource].append(action)
else:
role_info["resources"][resource] = [action]
role_data.append(role_info)
return role_data
def get_context_data(self, *args, **kwargs):
roles = self.filter_queryset(self.get_initial_queryset())
role_data = self.generate_resource_data(roles)
context = super().get_context_data(*args, **kwargs)
context['recordsTotal'] = len(role_data)
context['recordsFiltered'] = len(role_data)
context['data'] = role_data
context['result'] = 'ok'
return context
class AppRoleCreateOrUpdateView(LoginRequiredMixin, generic.View):
page_name = iam_constant.RESOURCE_IAM_ROLE
model = IAmRole
template_name = "module_iam/iam_role_add.html"
form_class = IAmPrincipalRoleAppResourceActionLinkForm
success_url = reverse_lazy("module_iam:role")
success_message = "Saved Successfully"
error_message = "An error occurred while saving the data."
def get_success_message(self):
self.success_message = (
f"Record {'Created' if not self.object else 'Updated'} Successfully"
)
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,
"operation": "Add" if not self.object else "Edit",
"app_resource_action": IAmAppResourceActionLink.objects.generate_app_resource_action_data(),
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
try:
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)
except Exception as e:
messages.error(request, str(e))
return redirect(self.success_url)
def post(self, request, *args, **kwargs):
try:
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()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
except Exception as e:
messages.error(self.request, str(e))
return redirect(self.success_url)
class AppRoleActionView(LoginRequiredMixin, ActionMixin):
model = IAmRole
class PrincipalProfileView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_DASHBOARD
model = IAmPrincipal
template_name = "module_iam/profile_details.html"
def get_object(self, queryset=None):
user = self.request.user.id
return get_object_or_404(self.model.objects.select_related("principal_type", "principal_source"), pk=user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
context["data_obj"] = self.get_object()
return context
class PrincipalProfileEditView(generic.View):
page_name = iam_constant.RESOURCE_MANAGE_DASHBOARD
model = IAmPrincipal
template_name = "module_iam/profile_details_edit.html"
form_class = ProfileEditForm
success_url = reverse_lazy("module_iam:profile_details")
success_message = "Saved Successfully"
error_message = "An error occurred while saving the data."
def get_success_message(self):
self.success_message = (
f"Record {'Created' if not self.object else 'Updated'} Successfully"
)
return self.success_message
def get_object(self):
return self.request.user
def get_context_data(self, **kwargs):
context = {
# "page_name": self.page_name,
"operation": "Edit",
"page_name": self.page_name
}
context.update(kwargs) # Include any additional context data passed to the view
return context
def get(self, request, *args, **kwargs):
# try:
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):
self.object = self.get_object()
form = self.form_class(request.POST, request.FILES, 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()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)

View File

@@ -0,0 +1,7 @@
from django import forms
from .models import PushNotification
class PushNotificationForm(forms.ModelForm):
class Meta:
model = PushNotification
fields = ('title', 'banner_image', 'message')

View File

@@ -0,0 +1,36 @@
# Generated by Django 5.0.2 on 2024-03-05 18:58
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='PushNotification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=True)),
('deleted', models.BooleanField(default=False)),
('created_on', models.DateTimeField(auto_now_add=True)),
('modified_on', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=255)),
('banner_image', models.ImageField(blank=True, null=True, upload_to='push_notification_images/')),
('message', models.TextField()),
('timestamp', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_created', to=settings.AUTH_USER_MODEL)),
('modified_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_modified', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'push_notification',
},
),
]

View File

@@ -1,3 +1,15 @@
from django.db import models
from module_iam.models import BaseModel
# Create your models here.
class PushNotification(BaseModel):
title = models.CharField(max_length=255)
banner_image = models.ImageField(upload_to='push_notification_images/', blank=True, null=True)
message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "push_notification"
def __str__(self):
return self.title

View File

@@ -0,0 +1,16 @@
from django.urls import path
from . import views
from django.views.generic import TemplateView
app_name = "module_notification"
urlpatterns = [
path("notification/", views.NotificationView.as_view(), name="notification"),
path("notification/add/", views.NotificationCreateOrUpdateView.as_view(), name="notification_add"),
path("notification/edit/<int:pk>", views.NotificationCreateOrUpdateView.as_view(), name="notification_edit"),
path("notification/list/", views.NotificationListJsonView.as_view(), name="notification_list"),
path("notification/action/", views.NotificationActionView.as_view(), name="notification_action"),
path("notification/send/", views.NotificationSendView.as_view(), name="notification_send"),
]

View File

@@ -1,3 +1,164 @@
from django.shortcuts import render
import logging
from datetime import datetime, timedelta
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.http import HttpRequest
from django.http.response import HttpResponse as HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views import generic
from module_iam.models import IAmPrincipal
from module_project.service import OneSignalService
from .models import PushNotification
from .forms import PushNotificationForm
from module_iam import iam_constant
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_project.mixins import ActionMixin
from module_project import constants
from module_project.utils import JsonResponseUtil
# Create your views here.
class NotificationView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_NOTIFICATION
resource = iam_constant.RESOURCE_MANAGE_NOTIFICATION
template_name = "module_notification/notification.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class NotificationListJsonView(BaseDatatableView):
model = PushNotification
columns = ["id", "title", "message", "active", "timestamp"]
order_columns = ["id", "title", "message", "active", "timestamp"]
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
class NotificationCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = iam_constant.RESOURCE_MANAGE_NOTIFICATION
resource = iam_constant.RESOURCE_MANAGE_NOTIFICATION
# Initialize the action as ACTION_CREATE (can change based on logic)
action = iam_constant.ACTION_CREATE # Default action
template_name = "module_notification/add_notification.html"
model = PushNotification
form_class = PushNotificationForm
success_url = reverse_lazy("module_notification:notification")
error_message = "An error occurred while saving the data."
# Determine the success message dynamically based on whether it's an update or create
def get_success_message(self):
self.success_message = (
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
)
return self.success_message
# Get the object (if exists) based on URL parameter 'pk
def get_object(self):
pk = self.kwargs.get("pk")
return get_object_or_404(self.model, pk=pk) if pk else None
# Add page_name and operation to the context
def get_context_data(self, **kwargs):
context = {
"page_name": self.page_name,
"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()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = iam_constant.ACTION_UPDATE
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):
print("Request data: ", request.POST)
self.object = self.get_object()
# If an object is found, change action to ACTION_UPDATE
if self.object is not None:
self.action = iam_constant.ACTION_UPDATE
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()
messages.success(self.request, self.get_success_message())
return redirect(self.success_url)
class NotificationActionView(ActionMixin):
model = PushNotification
class NotificationSendView(generic.View):
model = PushNotification
def post(self, request, *args, **kwargs):
id = request.POST.get("id")
obj = self.model.objects.filter(pk=int(id)).first()
# Get the current date and subtract 15 days
fifteen_days_ago = datetime.now() - timedelta(days=3)
# Filter the IAmPrincipal objects based on the last_login field being greater than or equal to fifteen_days_ago
player_ids = list(IAmPrincipal.objects.filter(last_login__gte=fifteen_days_ago).values_list('player_id', flat=True))
if not obj:
return JsonResponseUtil.error(message="No notification with such ID exists.")
print(f"data type is ============ {type(player_ids)}")
print(f"player id aare {player_ids}")
try:
notification = OneSignalService()
response = notification.send_notification(
headings=obj.title,
contents=obj.message,
# include_player_ids=["5643e132-5266-4dc2-9131-1b4a81f0cbd0"], # single player id
include_player_ids=player_ids,
)
print("pussh dtaa ===========", response)
except Exception as e:
print(f"Error is {e}")
error_response = {
"status": 400,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return JsonResponseUtil.error(**error_response)
return JsonResponseUtil.success(message="success")

View File

@@ -16,6 +16,11 @@ def get_current_date():
def get_current_time():
return datetime.now().time()
def get_date_range(days):
end_date = datetime.now()
start_date = end_date - timedelta(days=int(days))
return start_date, end_date
# Get current date in a specific timezone
from pytz import timezone
def get_current_date_in_timezone(timezone_str='UTC'):

View File

@@ -1,6 +1,8 @@
from django.db.models import Q
from django.http.response import JsonResponse
from django.core.paginator import Paginator
from .utils import JsonResponseUtil
from django.views import generic
class DatatablesMixin:
"""
@@ -83,4 +85,34 @@ class DatatablesMixin:
"recordsTotal": total_count,
"recordsFiltered": filtered_count,
"data": data
})
})
class ActionMixin(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 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 updated 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)

View File

@@ -15,9 +15,14 @@ from django.db.models import F
from django.db import transaction
from datetime import timedelta, time, datetime
from django.utils import timezone
# from onesignal_sdk.client import Client as OneSignalClient
import requests
from onesignal_sdk.client import Client as OneSignalClient
import logging
import onesignal
from onesignal.models import Notification
from onesignal.api import default_api
logger = logging.getLogger(__name__)
@@ -123,6 +128,7 @@ class SMSService:
# raise SMSError(message=str(e))
def create_otp(self, principal: IAmPrincipal, otp_purpose: str):
old_otp_change = IAmPrincipalOtp.objects.filter(principal=principal).update(is_used=True)
otp = IAmPrincipalOtp.objects.create(
principal=principal, otp_purpose=otp_purpose
)
@@ -175,81 +181,115 @@ class SMSService:
# self.send(phone_numbers, body)
return otp_code
# by using office onesignal package onesignal-python-api
class OneSignalService:
def __init__(self):
# class OneSignalNotificationService:
# Get the OneSignal app key and user key from the environment variables
self.configuration = onesignal.Configuration(
# """
# Class for sending notifications using the OneSignal API.
app_key=settings.ONESIGNAL_APP_ID,
api_key=settings.ONESIGNAL_REST_API_KEY
)
# Provides a convenient way to create and send notifications to OneSignal users,
# with features like targeting specific devices or segments, customizing notification content,
# and handling errors gracefully.
# Create an instance of the OneSignal API
self.api_client = onesignal.ApiClient(self.configuration)
self.api_instance = default_api.DefaultApi(self.api_client)
# **Parameters:**
def send_notification(self, headings, contents, include_player_ids=None):
# Create a notification object using a dictionary
notification = Notification(
app_id=self.configuration.app_key,
include_player_ids=include_player_ids,
headings={"en": headings},
contents={"en": contents}
)
try:
# Send the notification
response = self.api_instance.create_notification(
notification=notification,
async_req=True
)
except Exception as e:
raise Exception("Generic OneSignal error: {}".format(e))
print("complete service is succeesss")
return response
# - **app_id** (str): Your OneSignal App ID.
# - **rest_api_key** (str): Your OneSignal REST API Key.
# - **user_auth_key** (str): Your OneSignal User Auth Key.
# by using community packgae onesignal-sdk
class OneSignalNotificationService:
# **Keyword Arguments:**
"""
Class for sending notifications using the OneSignal API.
# This method accepts additional keyword arguments (`**kwargs`) to customize the notification
# further, including:
Provides a convenient way to create and send notifications to OneSignal users,
with features like targeting specific devices or segments, customizing notification content,
and handling errors gracefully.
# - `url` (str): URL to open when the notification is clicked.
# - `data` (dict): Custom data to be sent with the notification.
# - `buttons` (list): List of action buttons to display within the notification.
# - `send_after` (str): Timestamp for scheduling the notification.
# - `delayed_option` (dict): Option for delayed delivery (Android-specific).
# - `android_channel_id` (str): Channel ID for Android notifications.
# - `ios_sound` (str): Sound to play for iOS notifications.
# - `ios_badgeType` (str): Badge type for iOS notifications.
# - `ios_badgeCount` (int): Badge count for iOS notifications.
# - `ios_thread_id` (str): Thread ID to group notifications in iOS.
# - `android_background_layout` (str): Layout for background notifications on Android.
# - `android_group` (str): Group notification on Android.
# - `android_group_message` (str): Summary for grouped notifications on Android.
# - `android_group_summary` (str): Summary for grouped notifications on Android.
# - `android_led_color` (str): LED color for Android notifications.
# - `android_accent_color` (str): Accent color for Android notifications.
# - `android_visibility` (str): Visibility settings for Android notifications.
**Parameters:**
# **Example usage:**
- **app_id** (str): Your OneSignal App ID.
- **rest_api_key** (str): Your OneSignal REST API Key.
- **user_auth_key** (str): Your OneSignal User Auth Key.
# notification = OneSignalNotificationService()
# response = notification.send_notification(
# headings="Welcome",
# message="Thanks for signing up!",
# player_tokens=["PLAYER_TOKEN1", "PLAYER_TOKEN2"],
# url="https://yourwebsite.com/welcome",
# data={"user_id": 123},
# )
# """
**Keyword Arguments:**
# def __init__(self):
# self.config = OneSignalClient(
# app_id=settings.ONESIGNAL_APP_ID,
# rest_api_key=settings.ONESIGNAL_REST_API_KEY,
# user_auth_key=settings.ONESIGNAL_USER_AUTH_KEY
# )
This method accepts additional keyword arguments (`**kwargs`) to customize the notification
further, including:
# # Set up logging
# self.logger = logging.getLogger(__name__)
- `url` (str): URL to open when the notification is clicked.
- `data` (dict): Custom data to be sent with the notification.
- `buttons` (list): List of action buttons to display within the notification.
- `send_after` (str): Timestamp for scheduling the notification.
- `delayed_option` (dict): Option for delayed delivery (Android-specific).
- `android_channel_id` (str): Channel ID for Android notifications.
- `ios_sound` (str): Sound to play for iOS notifications.
- `ios_badgeType` (str): Badge type for iOS notifications.
- `ios_badgeCount` (int): Badge count for iOS notifications.
- `ios_thread_id` (str): Thread ID to group notifications in iOS.
- `android_background_layout` (str): Layout for background notifications on Android.
- `android_group` (str): Group notification on Android.
- `android_group_message` (str): Summary for grouped notifications on Android.
- `android_group_summary` (str): Summary for grouped notifications on Android.
- `android_led_color` (str): LED color for Android notifications.
- `android_accent_color` (str): Accent color for Android notifications.
- `android_visibility` (str): Visibility settings for Android notifications.
# def send_notification(self, headings, message, player_tokens=None, **kwargs):
# notification_obj = {
# "headings": {"en": headings},
# "contents": {"en": message},
# **kwargs
# }
**Example usage:**
# if player_tokens:
# notification_obj["include_player_ids"] = player_tokens
notification = OneSignalNotificationService()
response = notification.send_notification(
headings="Welcome",
message="Thanks for signing up!",
player_tokens=["PLAYER_TOKEN1", "PLAYER_TOKEN2"],
url="https://yourwebsite.com/welcome",
data={"user_id": 123},
)
"""
# try:
# response = self.config.send_notification(notification_obj)
# self.logger.info(f"Notification send successfully : {response}")
# return response
# except Exception as e:
# self.logger.error(f"OneSignal error {e}")
# raise Exception("Generic OneSignal error: {}".format(e))
def __init__(self):
self.config = OneSignalClient(
app_id=settings.ONESIGNAL_APP_ID,
rest_api_key=settings.ONESIGNAL_REST_API_KEY,
user_auth_key=settings.ONESIGNAL_USER_AUTH_KEY
)
# Set up logging
self.logger = logging.getLogger(__name__)
def send_notification(self, headings, message, player_tokens=None, **kwargs):
notification_obj = {
"headings": {"en": headings},
"contents": {"en": message},
**kwargs
}
if player_tokens:
notification_obj["include_player_ids"] = player_tokens
try:
response = self.config.send_notification(notification_obj)
self.logger.info(f"Notification send successfully : {response}")
return response
except Exception as e:
self.logger.error(f"OneSignal error {e}")
raise Exception("Generic OneSignal error: {}".format(e))

View File

@@ -52,6 +52,7 @@ LOCAL_APPS = [
"module_activity",
"module_cms",
"module_support",
"module_notification",
]
THIRD_PARTY_APPS = [
@@ -86,6 +87,7 @@ TEMPLATES = [
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'module_iam.iam_context_processors.iam_constants_context',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
@@ -151,7 +153,7 @@ SHORT_DATE_FORMAT = "d-m-Y"
TIME_FORMAT = "H:i p"
# otp expire time limit
OTP_EXPIRE_TIME = 10 # mins
OTP_EXPIRE_TIME = 5 # mins
APPEND_SLASH = True
LOGIN_REDIRECT_URL = "/iam/dashboard/"
@@ -212,6 +214,12 @@ EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD")
EMAIL_PORT = env.str("EMAIL_PORT")
EMAIL_USE_TLS = True
ONESIGNAL_APP_ID = env.str("ONESIGNAL_APP_ID")
ONESIGNAL_REST_API_KEY = env.str("ONESIGNAL_REST_API_KEY")
ONESIGNAL_USER_AUTH_KEY = env.str("ONESIGNAL_USER_AUTH_KEY")
# LOGGING
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/4.2/topics/logging/#logging
@@ -251,8 +259,8 @@ LOGGING = {
# jwt configuration
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html#settings
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": datetime.timedelta(days=10),
"REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=15),
"ACCESS_TOKEN_LIFETIME": datetime.timedelta(days=20),
"REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=30),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": False,
@@ -274,3 +282,8 @@ SIMPLE_JWT = {
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
"JTI_CLAIM": "jti",
}
SOCIAL_AUTH_APPLE_CLIENT_ID = '<YOUR_APPLE_CLIENT_ID>'
SOCIAL_AUTH_APPLE_CLIENT_SECRET = '<YOUR_APPLE_CLIENT_SECRET>'
SOCIAL_AUTH_APPLE_REDIRECT_URI = '<YOUR_APPLE_REDIRECT_URI>'

View File

@@ -30,11 +30,14 @@ urlpatterns = [
path('cms/', include('module_cms.urls')),
path('api/cms/', include('module_cms.api.urls')),
# path('support/', include('module_support.urls')),
path('support/', include('module_support.urls')),
path('api/support/', include('module_support.api.urls')),
path('activity/', include("module_activity.urls")),
path('api/activity/', include("module_activity.api.urls")),
path('notification/', include("module_notification.urls")),
# path('api/activity/', include("module_activity.api.urls")),
]
if settings.DEBUG:

View File

@@ -36,25 +36,25 @@ class ApiResponse:
if errors is not None:
response_data["errors"] = errors
return Response(response_data, status=status)
# @staticmethod
# def validation_error(errors, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY):
# return ApiResponse.error("Validation error", errors, status_code)
# def validation_error(errors, status=status.HTTP_422_UNPROCESSABLE_ENTITY):
# return ApiResponse.error("Validation error", errors, status)
class JsonResponseUtil:
@staticmethod
def success(message, data=None, status_code=200):
response_data = {"success": True, "status": status_code, "message": message}
def success(message, data=None, status=200):
response_data = {"success": True, "status": status, "message": message}
if data is not None:
response_data["data"] = data
return JsonResponse(response_data, status=status_code)
return JsonResponse(response_data, status=status)
@staticmethod
def error(message, errors=None, status_code=403):
response_data = {"success": False, "status": status_code, "message": message}
def error(message, errors=None, status=403):
response_data = {"success": False, "status": status, "message": message}
if errors is not None:
response_data["errors"] = errors
return JsonResponse(response_data, status=status_code)
return JsonResponse(response_data, status=status)
class RandomGenerator:

0
module_support/forms.py Normal file
View File

View File

@@ -1,11 +1,20 @@
from django.urls import path
from . import views
app_name = "manage_support"
app_name = "module_support"
urlpatterns = [
# path('contact_us/', views.ContactUsListView.as_view(), name='contact_us_list'),
# path('contact_us/reply/', views.ContactUsReplyView.as_view(), name='contact_us_reply'),
path('contact_us/', views.ContactUsView.as_view(), name="contact_us"),
path('contact_us/list/', views.ContactUsListJson.as_view(), name="contact_us_list"),
path('contact_us/reply/<int:id>/', views.ContactUsReplyView.as_view(), name='contact_us_reply'),
path('contact_us/action/', views.ContactUsActionView.as_view(), name='contact_us_action'),
path('contact_us/archive/list/', views.ContactUsArchiveView.as_view(), name='contact_us_archive'),
path('feedback/', views.FeedbackView.as_view(), name="feedback"),
path('feedback/list/', views.FeedbackListJson.as_view(), name="feedback_list"),
path('feedback/action/', views.FeedbackActionView.as_view(), name='feedback_action'),
# path('feedback/', views.FeedbackListView.as_view(), name='feedback_list'),
# path('feedback/delete/<int:pk>', views.FeedbackDeleteView.as_view(), name='feedback_delete'),

View File

@@ -1,3 +1,152 @@
from django.conf import settings
from django.shortcuts import render
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views import generic
from module_iam.models import IAmPrincipal
from module_iam import iam_constant
from module_project.service import EmailService
from .models import ContactUs, Feedback
from module_project.mixins import DatatablesMixin
from django_datatables_view.base_datatable_view import BaseDatatableView
from module_project.mixins import ActionMixin
from module_project import constants
from module_project.utils import JsonResponseUtil
# Create your views here.
class ContactUsView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_CONTACT_US
resource = None
action = None
template_name = "module_support/contact_us.html"
model = ContactUs
context_objext_name = "obj"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class ContactUsListJson(BaseDatatableView):
model = ContactUs
columns = ["id", "email_address", "subject", "message", "active", "deleted"]
order_columns = ["id", "email_address", "subject", "message", "active", "deleted"]
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)
)
for column in self.columns:
search_value = self.request.GET.get(f'columns[{self.columns.index(column)}][search][value]', None)
if search_value:
qs = qs.filter(**{f"{column}__icontains": search_value})
return qs
class ContactUsActionView(ActionMixin):
model = ContactUs
class ContactUsArchiveView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_CONTACT_US
resource = None
action = None
template_name = "module_support/contactus_archive_list.html"
model = ContactUs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class ContactUsReplyView(LoginRequiredMixin, generic.View):
page_name = iam_constant.RESOURCE_MANAGE_CONTACT_US
model = ContactUs
success_message = constants.DATA_SAVED
def post(self, request, *args, **kwargs):
id = self.kwargs.get("id")
message = request.POST.get("message")
if id or message:
try:
instance = self.model.objects.get(id=id)
instance.reply = message
instance.save()
email_service = EmailService(
subject=f"Reply of your inquiry - {instance.subject}",
body=message,
to=instance.email,
from_email=settings.EMAIL_HOST_USER,
)
email_service.send()
JsonResponseUtil.success(message=self.success_message)
except self.model.DoesNotExist:
JsonResponseUtil.error(message=constants.FAILURE, errors="Invalid contact us ID.")
except Exception as e:
JsonResponseUtil.error(message=constants.FAILURE, errors=str(e))
else:
JsonResponseUtil.error(message=constants.FAILURE, errors="Missing 'id' or 'message' in the request")
# Redirect to the desired URL after form submission
return JsonResponseUtil.success(message=constants.SUCCESS)
class FeedbackView(LoginRequiredMixin, generic.TemplateView):
page_name = iam_constant.RESOURCE_MANAGE_FEEDBACK
resource = iam_constant.RESOURCE_MANAGE_FEEDBACK
action = None
template_name = "module_support/feedback.html"
model = Feedback
context_objext_name = "obj"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class FeedbackListJson(BaseDatatableView):
model = Feedback
columns = ["id", "principal.email", "feedback_reaction", "comment", "active"]
order_columns = ["id", "principal.email", "feedback_reaction", "comment", "active"]
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(feedback_reaction__icontains=search_value)
| Q(comment__icontains=search_value)
)
return qs
class FeedbackActionView(ActionMixin):
model = Feedback
pass

View File

@@ -1,6 +1,12 @@
anyio==4.3.0
asgiref==3.7.2
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
colorama==0.4.6
colorlog==6.7.0
cryptography==42.0.5
defusedxml==0.7.1
Django==5.0.2
django-cors-headers==4.3.1
django-datatables-view==1.20.0
@@ -12,10 +18,26 @@ django-taggit==5.0.1
django-widget-tweaks==1.5.0
djangorestframework==3.14.0
djangorestframework-simplejwt==5.3.1
h11==0.14.0
httpcore==1.0.4
httpx==0.27.0
idna==3.6
mysqlclient==2.2.4
oauthlib==3.2.2
onesignal-python-api==2.0.2
onesignal-sdk==2.0.0
phonenumbers==8.13.30
pillow==10.2.0
pycparser==2.21
PyJWT==2.8.0
python-dateutil==2.9.0.post0
python3-openid==3.2.0
pytz==2024.1
requests==2.31.0
requests-oauthlib==1.3.1
six==1.16.0
sniffio==1.3.1
sqlparse==0.4.4
tqdm==4.66.2
tzdata==2023.4
urllib3==2.2.1

BIN
static/img/bowel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
static/img/foods.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7071 4.29289C12.0976 4.68342 12.0976 5.31658 11.7071 5.70711L6.41421 11H20C20.5523 11 21 11.4477 21 12C21 12.5523 20.5523 13 20 13H6.41421L11.7071 18.2929C12.0976 18.6834 12.0976 19.3166 11.7071 19.7071C11.3166 20.0976 10.6834 20.0976 10.2929 19.7071L3.29289 12.7071C3.10536 12.5196 3 12.2652 3 12C3 11.7348 3.10536 11.4804 3.29289 11.2929L10.2929 4.29289C10.6834 3.90237 11.3166 3.90237 11.7071 4.29289Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 703 B

View File

@@ -39,7 +39,7 @@
</a>
</li>
<li class="nav-item dropdown notification-dropdown">
<!-- <li class="nav-item dropdown notification-dropdown">
<a href="javascript:void(0);" class="nav-link dropdown-toggle" id="notificationDropdown" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg><span class="badge badge-success"></span>
</a>
@@ -152,18 +152,18 @@
</div>
</div>
</li>
</li> -->
<li class="nav-item dropdown user-profile-dropdown order-lg-0 order-1">
<a href="javascript:void(0);" class="nav-link dropdown-toggle user" id="userProfileDropdown" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div class="avatar-container">
<div class="avatar avatar-sm avatar-indicators avatar-online">
{% if not request.user.profile_photo%}
<img alt="avatar" src="{% static "img/profile_image.jpeg" %}" class="rounded-circle">
<img alt="avatar" src="{% static "img/default_profile.jpg" %}" class="rounded-circle">
{% elif request.user.profile_photo and request.user.profile_photo.url %}
<img alt="avatar" src="{{ request.user.profile_photo.url }}" class="rounded-circle">
{% else %}
<img alt="avatar" src="{% static "img/profile_image.jpeg" %}" class="rounded-circle">
<img alt="avatar" src="{% static "img/default_profile.jpeg" %}" class="rounded-circle">
{%endif%}
</div>
@@ -177,20 +177,23 @@
&#x1F44B;
</div>
<div class="media-body">
<h5>{{ user.get_full_name }}</h5>
<p>{{user.principal_type.name}}</p>
<h5>{{ user.first_name }}</h5>
</div>
</div>
</div>
<div class="dropdown-item">
<a href="">
<a href="{% url 'module_iam:profile_details'%}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg> <span>Profile</span>
</a>
</div>
<div class="dropdown-item">
<a href="">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg> <span>Log Out</span>
</a>
<form method="post" action="{% url 'module_auth:logout' %}" id="logout-form">
{% csrf_token %}
<a href="#" onclick="document.getElementById('logout-form').submit(); return false;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
<span>Log Out</span>
</a>
</form>
</div>
</div>

View File

@@ -27,15 +27,15 @@
{% comment %} <li class="menu menu-heading">
<div class="heading"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus"><line x1="5" y1="12" x2="19" y2="12"></line></svg><span>APPLICATIONS</span></div>
</li> {% endcomment %}
<li class="menu active">
<a href="" aria-expanded="false" class="dropdown-toggle">
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_DASHBOARD %}active{% endif %}">
<a href="{% url 'module_iam:dashboard'%}" aria-expanded="false" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</div>
</a>
</li>
<li class="menu ">
<li class="menu {% if page_name == iam_constants_context.RESOURCE_IAM_PRINCIPAL or page_name == iam_constants_context.RESOURCE_IAM_PRINCIPAL_GROUP or page_name == iam_constants_context.RESOURCE_IAM_GROUP or page_name == iam_constants_context.RESOURCE_IAM_ROLE %}active{% endif %}">
<a href="#iam" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">manage_accounts</span>
@@ -46,29 +46,29 @@
</div>
</a>
<ul class="collapse submenu list-unstyled show" id="iam" data-bs-parent="#accordionExample">
<li class="">
<li class="{% if page_name == iam_constants_context.RESOURCE_IAM_PRINCIPAL %}active{% endif %}">
<a href=""> IAM Principal </a>
</li>
<li class="">
<a href=""> IAM Principal Group </a>
<li class="{% if page_name == iam_constants_context.RESOURCE_IAM_PRINCIPAL_GROUP %}active{% endif %}">
<a href="{% url 'module_iam:principal_group_link' %}"> IAM Principal Group </a>
</li>
<li class="">
<a href=""> IAM Group </a>
<li class="{% if page_name == iam_constants_context.RESOURCE_IAM_GROUP %}active{% endif %}">
<a href="{% url 'module_iam:principal_group' %}"> IAM Group </a>
</li>
<li class="">
<a href=""> IAM Role </a>
<li class="{% if page_name == iam_constants_context.RESOURCE_IAM_ROLE %}active{% endif %}">
<a href="{% url 'module_iam:role' %}"> IAM Role </a>
</li>
</ul>
</li>
<li class="menu ">
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_USER %}active{% endif %}">
<a href="{% url 'module_auth:users'%}" aria-expanded="false" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">group</span>
<span>Manage Customer</span>
<span>Manage Users</span>
</div>
</a>
</li>
<li class="menu ">
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_CMS or page_name == iam_constants_context.RESOURCE_MANAGE_FAQS or page_name == iam_constants_context.RESOURCE_MANAGE_T_C or page_name == iam_constants_context.RESOURCE_MANAGE_PRIVACYPOLICY %}active{% endif %}">
<a href="#manage_cms" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">bookmark_manager</span>
@@ -79,19 +79,19 @@
</div>
</a>
<ul class="collapse submenu list-unstyled show" id="manage_cms" data-bs-parent="#accordionExample">
<li class="">
<li class="{% if page_name == iam_constants_context.RESOURCE_MANAGE_FAQS %}active{% endif %}">
<a href="{% url 'module_cms:faq'%}"> FAQ's</a>
</li>
<li class="">
<li class="{% if page_name == iam_constants_context.RESOURCE_MANAGE_T_C %}active{% endif %}">
<a href="{% url 'module_cms:terms_and_condition'%}"> Terms & Condition </a>
</li>
<li class="">
<li class="{% if page_name == iam_constants_context.RESOURCE_MANAGE_PRIVACYPOLICY %}active{% endif %}">
<a href="{% url 'module_cms:privacy_policy'%}">Privacy Policy</a>
</li>
</ul>
</li>
<li class="menu ">
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_SUPPORT or page_name == iam_constants_context.RESOURCE_MANAGE_CONTACT_US or page_name == iam_constants_context.RESOURCE_MANAGE_FEEDBACK %}active{% endif %}">
<a href="#manage_support" data-bs-toggle="collapse" aria-expanded="true" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">phone_in_talk</span>
@@ -102,11 +102,11 @@
</div>
</a>
<ul class="collapse submenu list-unstyled show" id="manage_support" data-bs-parent="#accordionExample">
<li class="">
<a href=""> Contact Us</a>
<li class="{% if page_name == iam_constants_context.RESOURCE_MANAGE_CONTACT_US %}active{% endif %}">
<a href="{% url 'module_support:contact_us'%}"> Contact Us</a>
</li>
<li class="">
<a href=""> Feedback </a>
<li class="{% if page_name == iam_constants_context.RESOURCE_MANAGE_FEEDBACK %}active{% endif %}">
<a href="{% url 'module_support:feedback'%}"> Feedback </a>
</li>
</ul>
</li>
@@ -122,14 +122,14 @@
</div>
</a>
</li> -->
<li class="menu">
<!-- <li class="menu">
<a href="./app-calendar.html" aria-expanded="false" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">analytics</span>
<span>Manage Reports</span>
</div>
</a>
</li>
</li> -->
<!-- <li class="menu ">
<a href="" aria-expanded="false" class="dropdown-toggle">
<div class="">
@@ -138,11 +138,11 @@
</div>
</a>
</li> -->
<li class="menu">
<a href="./app-calendar.html" aria-expanded="false" class="dropdown-toggle">
<li class="menu {% if page_name == iam_constants_context.RESOURCE_MANAGE_NOTIFICATION %}active{% endif %}">
<a href="{% url 'module_notification:notification'%}" aria-expanded="false" class="dropdown-toggle">
<div class="">
<span class="material-symbols-outlined">notifications</span>
<span>Notification</span>
<span>Manage Notification</span>
</div>
</a>
</li>

View File

@@ -37,7 +37,7 @@
<!-- END LOADER --> {% endcomment %}
<!-- BEGIN NAVBAR -->
{% include "base_structure/elements/header.html" with user=user %}
{% include "base_structure/elements/header.html" with user=request.user %}
<!-- END NAVBAR -->
<!-- BEGIN MAIN CONTAINER -->

View File

@@ -1,60 +1,70 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}{% endblock %}
{% block stylesheet %}
{% include "cdn_through_html/apexchart_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-xl-6 col-lg-6 col-sm-12 col-12 layout-spacing">
<div class="widget widget-card-four">
<div class="widget-content">
<div class="w-header">
<div class="w-info">
<h6 class="value">Active User</h6>
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<h6 class="font-weight-bold">No of Active Users</h6>
<h4 class="m-0 font-weight-bold">{{active_user_count}}</h4>
</div>
</div>
<div class="w-content">
<div class="w-info">
<p class="value">4578 </p>
<div class="pro-icon">
<img src="../src/assets/img/pro-icon.png">
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-6 col-lg-6 col-sm-12 col-12 layout-spacing">
<div class="widget widget-card-four">
<div class="widget-content">
<div class="w-header">
<div class="w-info">
<h6 class="value">Total User</h6>
<div class="col-md-6">
<div class="card d-flex">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<h6 class="font-weight-bold">No of Total Users</h6>
<h4 class="m-0 font-weight-bold">{{total_user_count}}</h4>
</div>
</div>
<div class="w-content">
<div class="w-info">
<p class="value">545454 </p>
<div class="pro-icon">
<img src="../src/assets/img/pro-icon.png">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12 col-12 layout-spacing">
<div class="widget widget-chart-three">
<div class="widget-heading">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5>Users Graph</h5>
</div>
</div>
<div class="widget-content">
<div id="user-chart">
</div>
</div>
</div>
</div>
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12 col-12 layout-spacing">
<div class="widget widget-chart-three">
<div class="widget-heading">
<div class="">
<h5 class="">Unique Visitors</h5>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5>Activity Graph</h5>
<select class="form-control w-25" name="year_select" id="year_select">
</select>
</div>
</div>
<div class="widget-content">
<div id="uniqueVisits"></div>
<div id="activity-chart">
</div>
</div>
</div>
</div>
@@ -63,4 +73,178 @@
{% endblock content %}
{% block javascript %}{% endblock %}
{% block javascript %}
{% include "cdn_through_html/apexchart_cdn_js.html" %}
<script>
// Function to populate the select element with years
function populateYears() {
var select = document.getElementById("year_select");
var currentYear = new Date().getFullYear();
// Add options for the current year and the last 5 years
for (var i = 0; i < 4; i++) {
var option = document.createElement("option");
option.value = currentYear - i;
option.textContent = currentYear - i;
select.appendChild(option);
}
}
var sLineArea = {
chart: {
height: 350,
type: 'area',
toolbar: {
show: false,
}
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth'
},
series: [
{
name: 'Meal',
data: [0,0,0,0,0,0,0,0,0,0,0,0]
},
{
name: 'Medication',
data: [0,0,0,0,0,0,0,0,0,0,0,0]
},
{
name: 'Symptoms',
data: [0,0,0,0,0,0,0,0,0,0,0,0]
},
{
name: 'Bowel',
data: [0,0,0,0,0,0,0,0,0,0,0,0]
}
],
xaxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
},
tooltip: {
x: {
format: 'dd/MM/yy HH:mm'
},
}
}
var activity_chart = new ApexCharts(
document.querySelector("#activity-chart"),
sLineArea
);
activity_chart.render();
function fetchActivityChartData(year) {
$.ajax({
url: `{% url "module_activity:chart_data"%}?year=2024`.replace("2024", year),
type: 'GET',
success: function(data) {
var monthlyCounts = data.data; // Access the 'data' property of the response
var mealData = monthlyCounts['Meal'];
var medicationData = monthlyCounts['Medication'];
var symptomData = monthlyCounts['Symptoms'];
var bowelData = monthlyCounts['Bowel'];
// Update the chart with the new data
sLineArea.series[0].data = mealData;
sLineArea.series[1].data = medicationData;
sLineArea.series[2].data = symptomData;
sLineArea.series[3].data = bowelData;
activity_chart.updateSeries(sLineArea.series);
},
error: function(xhr, status, error) {
console.error('Error fetching monthly counts:', error);
}
});
};
var sline = {
chart: {
height: 350,
type: 'line',
zoom: {
enabled: false
},
toolbar: {
show: false,
}
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'straight'
},
series: [{
name: "Registration",
data: [10, 41, 35, 51, 49, 62, 69, 91, 148]
}],
title: {
text: 'User registration by Month',
align: 'left'
},
grid: {
row: {
colors: ['#f1f2f3', 'transparent'], // takes an array which will be repeated on columns
opacity: 0.5
},
},
xaxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
}
}
var chart = new ApexCharts(
document.querySelector("#user-chart"),
sline
);
chart.render();
function fetchUserChartData(year) {
$.ajax({
url: `{% url "module_auth:user_count"%}?year=2024`.replace("2024", year),
type: 'GET',
success: function(data) {
var monthlyCounts = data.data; // Access the 'data' property of the response
// Update the chart with the new data
sline.series[0].data = monthlyCounts;
chart.updateSeries(sline.series);
},
error: function(xhr, status, error) {
console.error('Error fetching monthly counts:', error);
}
});
};
$(document).ready(function() {
// Call the function to populate the select element when the page loads
populateYears();
// Add event listener for change event on the select element
$("#year_select").change(function() {
var selectedYear = $(this).val();
fetchActivityChartData(selectedYear); // Call the fetchData function with the selected year
});
// Fetch data for the initial selected year
var initialYear = $("#year_select").val();
fetchActivityChartData(initialYear);
fetchUserChartData(initialYear)
});
</script>
{% endblock %}

View File

@@ -0,0 +1,3 @@
{%load static%}
<link href="{% static 'src/plugins/src/apex/apexcharts.css' %}" rel="stylesheet" type="text/css" />
<link href="{% static 'src/plugins/css/light/apex/custom-apexcharts.css' %}" rel="stylesheet" type="text/css" />

View File

@@ -0,0 +1,3 @@
{% load static%}
<script src="{% static 'src/plugins/src/apex/apexcharts.min.js' %}"></script>
<script src="{% static 'src/plugins/src/apex/custom-apexcharts.js' %}"></script>

View File

@@ -0,0 +1,45 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="m-2">{{operation}} {{page_title}}</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% endblock %}

View File

@@ -0,0 +1,229 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="card-title m-2">Chronic Condition Archive List</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending" style="width: 50.2656px;">
#</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Intolerances</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">For how long have you been experiencing this intolerance</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var actionUrl = '{% url "module_activity:chronic_condition_action" %}'
var mainUrl = '{% url "module_activity:chronic_condition_list" principal_id=principal_id%}?deleted_flag=True';
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance, viewUrl);
editClickEvent();
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'UnArchive',
className: "btn btn-dark buttons-unarchive",
action: unArchiveAction,
init: function(api, node, config){
$(node).hide();
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('#table tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-unarchive');
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle archive action
function unArchiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "unarchive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -24,7 +24,7 @@
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_activity:chronic_condition_add' principal_id=principal_id%}">Add</a>
</div>
</div>
@@ -84,8 +84,9 @@
// Define DataTable instance
var dataTableInstance
var actionUrl = '{% url "module_activity:chronic_condition_action" %}'
var mainUrl = '{% url "module_activity:chronic_condition_list" principal_id=principal_id%}?deleted_flag=false';
var mainUrl = '{% url "module_activity:chronic_condition_list" principal_id=principal_id%}?deleted_flag=False';
var editUrl = "{% url 'module_activity:chronic_condition_edit' principal_id=principal_id pk=0 %}"
var viewArchiveUrl = "{% url 'module_activity:chronic_condition_archive' principal_id=principal_id %}"
// Entry point
$(document).ready(function() {
@@ -133,7 +134,7 @@ function initializeDataTable(dataTableInstance, mainUrl) {
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
window.location.href = viewArchiveUrl;
}
}
],
@@ -183,7 +184,7 @@ function renderActions(data, type, row) {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item edit" href="javascript:void(0);" data-id="${row.id}">Edit</a>
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
</div>
</div>`;
}

View File

@@ -0,0 +1,229 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="card-title m-2">Intolerance Archive List</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending" style="width: 50.2656px;">
#</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Intolerances</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">For how long have you been experiencing this intolerance</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = '{% url "module_activity:intolerance_list" principal_id=principal_id%}?deleted_flag=True';
var actionUrl = '{% url "module_activity:intolerance_action" %}'
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance, viewUrl);
editClickEvent();
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'UnArchive',
className: "btn btn-dark buttons-unarchive",
action: unArchiveAction,
init: function(api, node, config){
$(node).hide();
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('#table tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-unarchive');
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle archive action
function unArchiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "unarchive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -24,7 +24,7 @@
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_activity:intolerance_add' principal_id=principal_id%}">Add</a>
</div>
</div>
@@ -84,19 +84,20 @@
// Define DataTable instance
var dataTableInstance
var actionUrl = '{% url "module_activity:intolerance_action" %}'
var mainUrl = '{% url "module_activity:intolerance_list" principal_id=principal_id%}?deleted_flag=false';
var mainUrl = '{% url "module_activity:intolerance_list" principal_id=principal_id%}?deleted_flag=False';
var editUrl = "{% url 'module_activity:intolerance_edit' principal_id=principal_id pk=0 %}"
var viewArchiveUrl = "{% url 'module_activity:intolerance_archive' principal_id=principal_id %}"
// Entry point
$(document).ready(function() {
dataTableInstance = initializeDataTable(dataTableInstance, mainUrl);
tableName = $('#table')
dataTableInstance = initializeDataTable(tableName, mainUrl);
editClickEvent();
activeSwitchEventListener();
});
// Function to initialize DataTable
function initializeDataTable(dataTableInstance, mainUrl) {
return $('#table').DataTable({
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
@@ -133,7 +134,7 @@ function initializeDataTable(dataTableInstance, mainUrl) {
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
window.location.href = viewArchiveUrl;
}
}
],
@@ -166,9 +167,7 @@ function renderCheckbox(data, type, row) {
// Render switch
function renderSwitch(data, type, row) {
console.log("data is ", data, "type is", typeof(data))
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
console.log("check attribute", + checkedAttribute)
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" id="form-custom-switch-checked' + row.id + '" data-id="' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
@@ -183,7 +182,7 @@ function renderActions(data, type, row) {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item edit" href="javascript:void(0);" data-id="${row.id}">Edit</a>
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
</div>
</div>`;
}
@@ -194,7 +193,7 @@ function initCompleteCallback() {
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('#table tbody input.archive-checkbox:checked').length;
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
archiveButton.toggle(checkedCount > 0);
});
@@ -216,7 +215,7 @@ function archiveAction() {
return;
}
// Get the IDs of the checked checkboxes
var userIds = checkedCheckboxes.map(function() {
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
@@ -236,7 +235,7 @@ function archiveAction() {
type: 'POST',
data: {
action: "archive",
ids: userIds,
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
@@ -306,14 +305,6 @@ function activeSwitchEventListener() {
}
// Function to handle click event for edit button
function editClickEvent() {
$('body').on('click', '.edit', function(){
var id =$(this).data('id');
console.log('Editing user with Id:', id);
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,229 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="card-title m-2">Past Treatment Archive List</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending" style="width: 50.2656px;">
#</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Intolerances</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">For how long have you been experiencing this intolerance</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = '{% url "module_activity:past_treatment_list" principal_id=principal_id%}?deleted_flag=True';
var actionUrl = '{% url "module_activity:past_treatment_action" %}'
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance, viewUrl);
editClickEvent();
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'UnArchive',
className: "btn btn-dark buttons-unarchive",
action: unArchiveAction,
init: function(api, node, config){
$(node).hide();
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('#table tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-unarchive');
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle archive action
function unArchiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "unarchive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -24,7 +24,7 @@
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_activity:past_treatment_add' principal_id=principal_id%}">Add</a>
</div>
</div>
@@ -46,9 +46,9 @@
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Symptoms</th>
colspan="1" style="width: 44.2344px;">User Past Treatment</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">For how long have you been experiencing this Symptoms</th>
colspan="1" style="width: 44.2344px;">Treatment date</th>
<th class="sorting text-center" tabindex="5" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
<th class="sorting text-center" tabindex="6" aria-controls="style-3"
@@ -84,8 +84,9 @@
// Define DataTable instance
var dataTableInstance
var actionUrl = '{% url "module_activity:past_treatment_action" %}'
var mainUrl = '{% url "module_activity:past_treatment_list" principal_id=principal_id%}?deleted_flag=false';
var mainUrl = '{% url "module_activity:past_treatment_list" principal_id=principal_id%}?deleted_flag=False';
var editUrl = "{% url 'module_activity:past_treatment_edit' principal_id=principal_id pk=0 %}"
var viewArchiveUrl = "{% url 'module_activity:past_treatment_archive' principal_id=principal_id %}"
// Entry point
$(document).ready(function() {
@@ -133,7 +134,7 @@ function initializeDataTable(dataTableInstance, mainUrl) {
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
window.location.href = viewArchiveUrl;
}
}
],
@@ -183,7 +184,7 @@ function renderActions(data, type, row) {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item edit" href="javascript:void(0);" data-id="${row.id}">Edit</a>
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
</div>
</div>`;
}

View File

@@ -0,0 +1,229 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="card-title m-2">Symptoms Archive List</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending" style="width: 50.2656px;">
#</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">User Intolerances</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">For how long have you been experiencing this intolerance</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = '{% url "module_activity:symptoms_list" principal_id=principal_id%}?deleted_flag=True';
var actionUrl = '{% url "module_activity:symptoms_action" %}'
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance, viewUrl);
editClickEvent();
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "name", className: "text-center" },
{ data: "duration", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'UnArchive',
className: "btn btn-dark buttons-unarchive",
action: unArchiveAction,
init: function(api, node, config){
$(node).hide();
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('#table tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-unarchive');
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle archive action
function unArchiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "unarchive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -24,7 +24,7 @@
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="">Add User</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_activity:symptoms_add' principal_id=principal_id%}">Add</a>
</div>
</div>
@@ -84,7 +84,9 @@
// Define DataTable instance
var dataTableInstance
var actionUrl = '{% url "module_activity:symptoms_action" %}'
var mainUrl = '{% url "module_activity:symptoms_list" principal_id=principal_id%}?deleted_flag=false';
var mainUrl = '{% url "module_activity:symptoms_list" principal_id=principal_id%}?deleted_flag=False';
var editUrl = "{% url 'module_activity:symptoms_edit' principal_id=principal_id pk=0 %}"
var viewArchiveUrl = "{% url 'module_activity:symptoms_archive' principal_id=principal_id %}"
// Entry point
$(document).ready(function() {
@@ -132,8 +134,7 @@ function initializeDataTable(dataTableInstance, mainUrl) {
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
window.location.href = viewArchiveUrl;
}
}
],
@@ -183,7 +184,7 @@ function renderActions(data, type, row) {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item edit" href="javascript:void(0);" data-id="${row.id}">Edit</a>
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
</div>
</div>`;
}

View File

@@ -1,13 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Password Reset</title>
<meta charset="UTF-8">
<title>Password Reset</title>
<style>
body {
font-family: Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
color: #333;
margin: 0;
padding: 0;
}
p {
margin: 16px 0;
}
strong {
font-weight: bold;
}
</style>
</head>
<body>
<p>Need to reset your password?</p>
<p>Use your secret code:</p>
<div style="max-width: 600px; margin: 0 auto;">
<p>Hello {{name}},</p>
<p>It looks like you've requested a password reset for your account.</p>
<p>To reset your password, please use the following secret code:</p>
<p><strong>{{ code }}</strong></p>
<p>If you did not forget your password, you can ignore this email.</p>
<p>If you didn't request a password reset, you can safely ignore this email.</p>
<p>Thank you,</p>
<p>The Support Team</p>
</div>
</body>
</html>
</html>

View File

@@ -0,0 +1,45 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="m-2">{{operation}} User</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% endblock %}

View File

@@ -10,6 +10,13 @@
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
<style>
.food-column {
background-color: #e0eff9;
height: 100%;
border-radius: 5px;
}
</style>
{% endblock %}
{% block content %}
@@ -204,6 +211,21 @@
</div>
</div>
<div class="tab-pane fade" id="pills-tab3" role="tabpanel" aria-labelledby="pills-tab3-tab" tabindex="2">
<div class="d-flex justify-content-sm-end justify-content-center mb-3">
<div class="dropdown">
<a class="dropdown-toggle btn btn-primary" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filter
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink" style="">
<a class="dropdown-item" href="javascript:void(0)" onclick="getReportData(7)">Last 7 days</a>
<a class="dropdown-item" href="javascript:void(0)" onclick="getReportData(20)">Last 20 days</a>
<a class="dropdown-item" href="javascript:void(0)" onclick="getReportData(40)">Last 40 days</a>
<a class="dropdown-item" href="javascript:void(0)" onclick="getReportData(60)">Last 60 days</a>
</div>
</div>
</div>
<div class="tab-content" id="pills-tab3Content">
</div>
</div>
</div>
@@ -237,10 +259,13 @@ var mealUrl = "{% url 'module_activity:meal_detail' pk=0 %}"
var medicationUrl = "{% url 'module_activity:medication_detail' pk=0 %}"
var bowelUrl = "{% url 'module_activity:bowel_detail' pk=0 %}"
var symptomUrl = "{% url 'module_activity:meal_symptom_detail' pk=0 %}"
var reportUrl = "{% url 'module_activity:report_data' principal_id=obj.id %}?date_range=7"
// Entry point
$(document).ready(function() {
dataTableInstance = initializeDataTable(dataTableInstance, mainUrl);
getReportData();
});
// Function to initialize DataTable
@@ -333,5 +358,133 @@ function reloadDataTable() {
}
function getReportData(timeRange){
var url = timeRange ? reportUrl.replace("7", timeRange) : reportUrl
$('#pills-tab3Content').empty()
$.ajax({
url: url,
type: 'GET',
success: function(response) {
console.log("reposne is ", response);
if (response.status == 200){
setReportContent(response.data)
}
if (response.status == 204){
console.log(response.message)
const errorCard = $('<div class="card card-danger text-center mt-3 p-4 tab-pane-content"></div>');
const title = $('<h5 class="mb-3"></h5>').text('Error');
const message = $('<p></p>').text(response.message);
errorCard.append(title);
errorCard.append(message);
$('#pills-tab3Content').append(errorCard);
}
},
error: function(response) {
}
});
}
function setReportContent(data) {
// Clear previous content
$('#pills-tab3Content').empty();
// Foods to Avoid and Bowel Report
if (data.food_avoid || data.highest_stool) {
const section = $('<div class="row tab-pane-content d-flex justify-content-center gap-5 mb-5"></div>');
if (data.food_avoid) {
createFoodToAvoid(section, data.food_avoid);
}
if (data.highest_stool) {
createBowelReport(section, data.highest_stool);
}
$('#pills-tab3Content').append(section);
}
const tableSection = $('<div class="row tab-pane-content"></div>');
// Same Foods to Avoid
if (data.same_food_avoid) {
createTable('Same Foods to Avoid', tableSection, data.same_food_avoid.food);
}
// Meal Symptoms Recorded
if (data.symptoms_frequency) {
createTable('Meal Symptoms Recorded', tableSection, data.symptoms_frequency);
}
// Recorded Stool Type
if (data.stool_type) {
createTable('Recorded Stool Type', tableSection, data.stool_type);
}
$('#pills-tab3Content').append(tableSection);
}
function createFoodToAvoid(parent, content) {
const col = $('<div class="col-md-3 tab-pane-content food-column text-center card d-flex flex-column justify-content-center align-items-center gap-4 py-4"><div class="h-100"></div></div>');
const title = $('<h5></h5>').text('Foods to Avoid');
const img = $('<img height="100" width="100">').attr('src', "{% static 'img/foods.png' %}");
const h4 = $('<h3></h4>').text(content);
const h6 = $('<h6></h6>').html(
'Based on the Symptoms added within the last 7 days, <b>' + content + '</b> should be avoided.'
);
col.append(title);
col.append(img);
col.append(h4);
col.append(h6);
parent.append(col);
}
function createBowelReport(parent, content) {
const col = $('<div class="col-md-3 tab-pane-content food-column text-center card d-flex flex-column justify-content-center align-items-center gap-4 py-4"><div class="h-100"></div></div>');
const title = $('<h5></h5>').text('Bowel Report');
const img = $('<img height="100" width="100">').attr('src', "{% static 'img/bowel.png' %}");
const h4 = $('<h3></h4>').text(content);
const h6 = $('<h6></h6>').text('Your most recorded stool type is ' + content);
col.append(title);
col.append(img);
col.append(h4);
col.append(h6);
parent.append(col);
}
function createTable(title, parent, data) {
const col = $('<div class="col-md-4 tab-pane-content"></div>');
const table = $('<table class="table table-sm tab-pane-content mb-3"></table>');
const thead = $('<thead></thead>');
const tbody = $('<tbody></tbody>');
const headerRow = $('<tr></tr>');
const titleTh = $('<th colspan="2"></th>').text(title);
headerRow.append(titleTh);
thead.append(headerRow);
for (const [key, value] of Object.entries(data)) {
const row = $('<tr></tr>');
const keyTd = $('<td></td>').text(key);
const valueTd = $('<td></td>').text(value);
row.append(keyTd);
row.append(valueTd);
tbody.append(row);
}
col.append(table)
table.append(thead);
table.append(tbody);
parent.append(col)
}
</script>
{% endblock %}

View File

@@ -0,0 +1,257 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="card-title m-2">Users Archive List</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending" style="width: 50.2656px;">
#</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">Name</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">Email</th>
<th class="sorting text-center" tabindex="3" aria-controls="style-3"
style="width: 79.7969px;">Phone No.</th>
<th class="sorting text-center" tabindex="4" aria-controls="style-3"
style="width: 79.7969px;">Date of Birth</th>
</tr>
</thead>
<thead id="filterboxrow">
<tr>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th rowspan="1" colspan="1">Email</th>
<th rowspan="1" colspan="1">Name</th>
<th rowspan="1" colspan="1">Phone No.</th>
<th rowspan="1" colspan="1">Date of Birth</th>
</tr>
<thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_auth:users_list' %}?deleted_flag=True"
var actionUrl = "{% url 'module_auth:users_action' %}"
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance, viewUrl);
editClickEvent();
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "first_name", className: "text-center" },
{ data: "email", className: "text-center" },
{ data: "phone_no", className: "text-center" },
{ data: "date_of_birth", className: "text-center" },
],
debug: true,
columnDefs: [
{ targets: [1, 2, 3, 4, 5], searchable: true, orderable: true },
{ targets: [0], searchable: false, orderable: false }
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'UnArchive',
className: "btn btn-dark buttons-unarchive",
action: unArchiveAction,
init: function(api, node, config){
$(node).hide();
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add individual search inputs to the first row of the thead section
$('thead#filterboxrow th').each(function (index) {
var title = $(this).text();
var input = $('<input type="text" class="form-control" placeholder="Search ' + title + '"/>')
.on('keyup change', function () {
if (api.column(index).search() !== this.value) {
api.column(index).search(this.value).draw();
}
});
$(this).empty().append(input);
});
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('#table tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-unarchive');
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle archive action
function unArchiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "unarchive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -3,6 +3,7 @@
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/tabs_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
@@ -24,9 +25,10 @@
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="">Add User</a>
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_auth:user_add' %}">Add User</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
@@ -70,10 +72,10 @@
<th class="invisible" rowspan="1" colspan="1">Active</th>
<th class="invisible" rowspan="1" colspan="1">Action</th>
</tr>
<thead>
<tbody>
<thead>
<tbody>
</tbody>
</tbody>
</table>
</div>
@@ -98,24 +100,27 @@
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_auth:users_list' %}?deleted_flag=False"
var editUrl = "{% url 'module_auth:user_edit' pk=0 %}"
var actionUrl = "{% url 'module_auth:users_action' %}"
var viewUrl = '{% url "module_auth:user_view" id=0 %}';
var viewArchiveUrl = "{% url 'module_auth:user_archive' %}"
// Entry point
$(document).ready(function() {
var viewUrl = '{% url "module_auth:user_view" id=0 %}';
dataTableInstance = $('#table');
initializeDataTable(dataTableInstance);
viewClickEvent(viewUrl);
editClickEvent();
deleteClickEvent();
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
activeSwitchEventListener();
});
// Function to initialize DataTable
function initializeDataTable(dataTableInstance) {
return dataTableInstance.DataTable({
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: "{% url 'module_auth:users_list'%}",
url: mainUrl,
type: "GET",
},
columns: [
@@ -148,7 +153,10 @@ function initializeDataTable(dataTableInstance) {
{
text: 'View Archive List',
className: "btn btn-dark ",
action: redirectToArchive }
action: function(){
window.location.href = viewArchiveUrl;
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
@@ -166,7 +174,7 @@ function initializeDataTable(dataTableInstance) {
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.Datatable().ajax.reload();
dataTableInstance.ajax.reload();
}
// Render checkbox
@@ -179,10 +187,10 @@ function renderCheckbox(data, type, row) {
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data ? 'checked' : '';
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" data-id="'+ row.id +'" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
@@ -194,17 +202,12 @@ function renderActions(data, type, row) {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item view" href="javascript:void(0);" data-id="${row.id}">View</a>
<a class="dropdown-item edit" href="javascript:void(0);" data-id="${row.id}">Edit</a>
<a class="dropdown-item delete" href="javascript:void(0);" data-id="${row.id}">Delete</a>
<a class="dropdown-item view" href="${ viewUrl.replace('0',row.id)}" data-id="${row.id}">View</a>
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
</div>
</div>`;
}
// Function to handle archive action
function archiveAction() {
window.location.href = '/archive';
}
// Function to redirect to archive
@@ -238,39 +241,107 @@ function initCompleteCallback() {
}
// Function to handle click event for view button
function viewClickEvent(viewUrl) {
$('body').on('click', '.view', function(){
var id =$(this).data('id');
window.location.href = viewUrl.replace('0', id);
console.log('Viewing user with Id:', id);
});
}
// Function to handle click event for edit button
function editClickEvent() {
$('body').on('click', '.edit', function(){
var id =$(this).data('id');
console.log('Editing user with Id:', id);
});
}
// Function to handle click event for delete button
function deleteClickEvent() {
$('body').on('click', '.delete', function() {
var id = $(this).data('id');
console.log('Deleting user with ID:', id);
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'Are you sure?',
text: 'Once deleted, you will not be able to recover this user!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
console.log("success");
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});

View File

@@ -7,6 +7,7 @@
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
@@ -32,14 +33,17 @@
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="faqs_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="faqs" class="table style-3 dt-table-hover dataTable" role="grid"
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
@@ -51,67 +55,21 @@
style="width: 44.2344px;">Answer</th>
<th class="sorting text-center" tabindex="3" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
<th class="sorting text-center" tabindex="4" aria-controls="style-3"
style="width: 79.7969px;">Deleted</th>
<th class="sorting text-center" tabindex="5" aria-controls="style-3"
style="width: 79.7969px;">Action</th>
</tr>
</thead>
<thead id="filterboxrow">
<tr>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th rowspan="1" colspan="1">Question</th>
<th rowspan="1" colspan="1">Answer</th>
<th class="invisible" rowspan="1" colspan="1">Active</th>
<th class="invisible" rowspan="1" colspan="1">Deleted</th>
<th class="invisible" rowspan="1" colspan="1">Action</th>
</tr>
<thead>
<tbody>
{% comment %} {% for data_obj in faqs_obj%}
<tr role="row">
<td class="text-center sorting_1"> {{data_obj.id}}</td>
<td>
<button type="button" class="btn btn-info mb-2 me-4" data-bs-toggle="modal" data-bs-target="#faqmodal" onclick="faqModal('{{data_obj.question}}','{{data_obj.answer}}')">
View
</button>
</td>
<td>{{data_obj.created_on}}</td>
<td>{{data_obj.modified_on}}</td>
<td class="text-center">
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.active}}</span>
</td>
<td class="text-center">
<ul class="table-controls">
<li><a href="{% url 'module_cms:faq_edit' data_obj.id %}" class="bs-tooltip"
data-bs-toggle="tooltip" data-bs-placement="top" title=""
data-original-title="Edit" data-bs-original-title="Edit"
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-edit-2 p-1 br-8 mb-1">
<path
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
</path>
</svg></a></li>
<li><a href="{% url 'module_cms:faq_edit' data_obj.id %}" class="bs-tooltip"
data-bs-toggle="tooltip" data-bs-placement="top" title=""
data-original-title="Delete" data-bs-original-title="Delete"
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-trash p-1 br-8 mb-1">
<polyline points="3 6 5 6 21 6"></polyline>
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
</path>
</svg></a></li>
</ul>
</td>
</tr>
{% endfor %} {% endcomment %}
</tbody>
</table>
@@ -160,164 +118,287 @@
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
{% comment %} var faqs = $('#faqs').DataTable({
processing: true,
serverside: true,
ajax: {
url: "{% url 'module_cms:faq_list'%}",
type: "GET", // Corrected typo here
data: function(d) {
// Add custom data to the request if needed
return d
},
error: function(xhr, error, thrown) {
console.log('Ajax error:', error);
},
success: function(response) {
console.log('Ajax response:', response);
}
},
columns: [
{ data: "id" },
{ data: "question" },
{ data: "answer" },
{ data: "active" },
{ data: "deleted" }
],
debug: true, // Enable debugging mode
columnDefs: [
{
targets: [0, 1, 2, 3, 4],
searchable: true,
orderable: true
}
], // Added comma here
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: "Results : _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10
}); {% endcomment %}
var faqs = $('#faqs').DataTable({
processing: true,
serverSide: true,
ajax: {
url: "{% url 'module_cms:faq_list'%}",
type: "GET",
},
columns: [
{
data: "id",
className : "text-center"
},
{ data: "question" },
{ data: "answer" },
{
data: "active",
className : "text-center",
render: function (data, type, row) {
// Check if active is true or false and set checked attribute accordingly
var checkedAttribute = data ? 'checked' : '';
// Generate switch HTML
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
},
{
data: "deleted",
className : "text-center"
},
{
data: null,
className: "text-center", // Add class to action column
render: function (data, type, row) {
return `
<div class="dropdown">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink${row.id}" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item" href="javascript:void(0);">View</a>
<a class="dropdown-item" href="javascript:void(0);">Edit</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>`;
}
}
],
debug: true, // Enable debugging mode
columnDefs: [
{
targets: [0, 1, 2, 3, 4],
searchable: true,
orderable: true
},
{
targets: -1, // Targeting the last column (action column)
searchable: false,
orderable: false
}
], // Added comma here
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: function () {
var api = this.api();
// Add individual search inputs to the first row of the thead section
$('thead#filterboxrow th').each(function (index) {
var title = $(this).text();
var input = $('<input type="text" class="form-control" placeholder="Search ' + title + '"/>')
.on('keyup change', function () {
if (api.column(index).search() !== this.value) {
api
.column(index)
.search(this.value)
.draw();
}
});
$(this).empty().append(input);
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_cms:faq_list' %}?deleted_flag=False"
var editUrl = "{% url 'module_cms:faq_edit' pk=0 %}"
var actionUrl = "{% url 'module_cms:faq_action' %}"
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance)
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "question" },
{ data: "answer" },
{ data: "active", className: "text-center", render: renderSwitch },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{
"targets": [1,2],
"render": function (data, type, row) {
// Adjust the length of text you want to show before truncating
var maxLength = 40;
// Truncate the text if it exceeds the maxLength
var truncatedText = data.length > maxLength ? data.substr(0, maxLength) + '...' : data;
// Return the truncated text
return truncatedText;
}
},
{
targets: [1, 2, 3],
searchable: true,
orderable: true
},
{
targets: [0,-1], // Targeting the last column (action column)
searchable: false,
orderable: false
},
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'Archive',
className: "btn btn-dark buttons-archive",
action: archiveAction,
init: function(api, node, config){
$(node).hide();
}
},
{
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" data-id="'+ row.id +'" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<div class="dropdown">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink${row.id}" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item view" href="javascript:void(0);" data-id="${row.id}">View</a>
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
</div>
</div>`;
}
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add individual search inputs to the first row of the thead section
$('thead#filterboxrow th').each(function (index) {
var title = $(this).text();
var input = $('<input type="text" class="form-control" placeholder="Search ' + title + '"/>')
.on('keyup change', function () {
if (api.column(index).search() !== this.value) {
api.column(index).search(this.value).draw();
}
});
$(this).empty().append(input);
});
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
console.log("checkbox is checked", + checkedCount)
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle click event for view button
function viewClickEvent(dataTableInstance) {
$('body').on('click', '.view', function(){
var id =$(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
var question = rowData.question;
var answer = rowData.answer;
console.log(question, answer)
// Set the data in the modal content
$('#questionData').text(question);
$('#answerData').text(answer);
// Show the modal
$('#faqmodal').modal('show');
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
}
<!-- Faq modal show and set data-->
function faqModal(question, answer) {
// Set the data in the modal content
$("#questionData").text(question);
$("#answerData").text(answer);
// Show the modal
$('#faqmodal').modal('show');
}
</script>
</script>
{% endblock %}

View File

@@ -0,0 +1,54 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>{{operation}} Faq</h3>
</div>
<div class="col text-end">
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="POST">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
<script>
</script>
{% endblock %}

View File

@@ -0,0 +1,353 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Principal Group</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_iam:principal_group_add' %}">Add Group</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column text-center sorting_asc" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
aria-label=" Record Id : activate to sort column descending"
style="width: 69.2656px;"> Id </th>
<th class="checkbox-column text-center sorting_asc" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
aria-label=" Record Id : activate to sort column descending"
style="width: 69.2656px;"> Id </th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
colspan="1" aria-label="Image: activate to sort column ascending"
style="width: 44.2344px;">Group Name</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
colspan="1" aria-label="Image: activate to sort column ascending"
style="width: 44.2344px;">Role</th>
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
aria-label="First Name: activate to sort column ascending"
style="width: 79.7969px;">Active</th>
<th class="text-center dt-no-sorting sorting" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1"
aria-label="Action: activate to sort column ascending"
style="width: 51.625px;">Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_iam:principal_group_list' %}?deleted_flag=False"
var editUrl = "{% url 'module_iam:principal_group_edit' pk=0 %}"
var actionUrl = "{% url 'module_iam:principal_group_action' %}"
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance)
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "name" },
{ data: "roles", render: renderRole},
{ data: "active", className: "text-center", render: renderSwitch },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{
targets: [1, 2],
searchable: true,
orderable: true
},
{
targets: [0,-1], // Targeting the last column (action column)
searchable: false,
orderable: false
},
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'Archive',
className: "btn btn-dark buttons-archive",
action: archiveAction,
init: function(api, node, config){
$(node).hide();
}
},
{
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
function renderRole(data, type, row) {
if (type === 'display' && row.roles) {
let html = '<ul>';
for (const [name] of Object.entries(row.roles)) {
html += `<li class="mb-1"><span class="badge badge-primary">${name}</span></li>`;
}
html += '</ul>';
return html;
} else if (type === 'display' && !row.roles) {
return '<span class="badge badge-danger">No Permission assigned</span>';
} else {
return '';
}
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" data-id="'+ row.id +'" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<ul class="table-controls">
<li>
<a href="${ editUrl.replace('0',row.id)}" class="bs-tooltip edit" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit" data-bs-original-title="Edit" aria-label="Edit">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2 p-1 br-8 mb-1">
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
</svg>
</a>
</li>
</ul>`;
}
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
console.log("checkbox is checked", + checkedCount)
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle click event for view button
function viewClickEvent(dataTableInstance) {
$('body').on('click', '.view', function(){
var id =$(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,64 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>{{operation}} Principal Group</h3>
</div>
<div class="col text-end">
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post" autocomplete="off">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
$(document).ready(function() {
$('.js-example-basic-multiple').select2({
placeholder: 'Select options',
allowClear: true,
tags: true, // Allow the user to enter custom tags
tokenSeparators: [',', ' '], // Customize token separators
closeOnSelect: false // Keep the dropdown open after selection
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,431 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/tabs_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Manage Principal</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'manage_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
</div>
</div>
<div class="row">
<div id="tabsSimple" class="col-xl-12 col-12 layout-spacing">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area p-3">
<div class="simple-pill">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-tab1-tab" data-bs-toggle="pill" data-bs-target="#pills-tab1" type="button" role="tab" aria-controls="pills-tab1" aria-selected="false">Admin</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-tab2-tab" data-bs-toggle="pill" data-bs-target="#pills-tab2" type="button" role="tab" aria-controls="pills-tab2" aria-selected="true">SubAdmin</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade active show" id="pills-tab1" role="tabpanel" aria-labelledby="pills-tab1-tab" tabindex="0">
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Time</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Meal</th>
<th class="sorting text-center" tabindex="3" aria-controls="style-3"
style="width: 79.7969px;">Medication</th>
<th class="sorting text-center" tabindex="4" aria-controls="style-3"
style="width: 79.7969px;">Bowel Movement</th>
<th class="sorting text-center" tabindex="5" aria-controls="style-3"
style="width: 79.7969px;">Symptoms</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-tab2" role="tabpanel" aria-labelledby="pills-tab2-tab" tabindex="1">
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table2_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table2" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column text-center sorting_asc" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
style="width: 69.2656px;"> Record Id </th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 79.7969px;">Name</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 143.516px;">Email</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 143.516px;">Permission</th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
style="width: 98.875px;">Principal Type</th>
<th class="text-center dt-no-sorting" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1"
style="width: 51.625px;">Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_iam:role_list' %}?deleted_flag=False"
var editUrl = "{% url 'module_iam:role_edit' pk=0 %}"
var actionUrl = "{% url 'module_iam:role_action' %}"
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance)
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "first_name" },
{ data: "email" },
{
data: null,
render: function(){
return `<ul><span class="badge badge-success">All Access Permission</span></ul>`
}
},
{ data: "principal_type__name", className: "text-center"},
{ data: "is_active", className: "text-center", render: renderSwitch },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{
targets: [1, 2],
searchable: true,
orderable: true
},
{
targets: [3],
searchable: true,
orderable: false
},
{
targets: [0,-1], // Targeting the last column (action column)
searchable: false,
orderable: false
},
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'Archive',
className: "btn btn-dark buttons-archive",
action: archiveAction,
init: function(api, node, config){
$(node).hide();
}
},
{
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
function renderResources(data, type, row) {
if (type === 'display' && row.resources) {
let html = '<ul>';
for (const [resource, actions] of Object.entries(row.resources)) {
html += `<li class="mb-1"><span class="badge badge-primary">${resource}</span>`;
for (const action of actions) {
html += `<span class="badge badge-secondary">${action}</span>`;
}
html += '</li>';
}
html += '</ul>';
return html;
} else if (type === 'display' && !row.resources) {
return '<span class="badge badge-danger">No Permission assigned</span>';
} else {
return '';
}
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" data-id="'+ row.id +'" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<ul class="table-controls">
<li>
<a href="${ editUrl.replace('0',row.id)}" class="bs-tooltip edit" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit" data-bs-original-title="Edit" aria-label="Edit">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2 p-1 br-8 mb-1">
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
</svg>
</a>
</li>
</ul>`;
}
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
console.log("checkbox is checked", + checkedCount)
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle click event for view button
function viewClickEvent(dataTableInstance) {
$('body').on('click', '.view', function(){
var id =$(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,362 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Role</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_iam:role_add' %}">Add Role</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column text-center sorting_asc" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
aria-label=" Record Id : activate to sort column descending"
style="width: 69.2656px;"> Id </th>
<th class="checkbox-column text-center sorting_asc" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
aria-label=" Record Id : activate to sort column descending"
style="width: 69.2656px;"> Id </th>
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
colspan="1" aria-label="Image: activate to sort column ascending"
style="width: 44.2344px;">Role Name</th>
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
aria-label="First Name: activate to sort column ascending"
style="width: 79.7969px;">Permission</th>
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
aria-label="First Name: activate to sort column ascending"
style="width: 79.7969px;">Active</th>
<th class="text-center dt-no-sorting sorting" tabindex="0"
aria-controls="style-3" rowspan="1" colspan="1"
aria-label="Action: activate to sort column ascending"
style="width: 51.625px;">Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_iam:role_list' %}?deleted_flag=False"
var editUrl = "{% url 'module_iam:role_edit' pk=0 %}"
var actionUrl = "{% url 'module_iam:role_action' %}"
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance)
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "name" },
{ data: "resource", render: renderResources },
{ data: "active", className: "text-center", render: renderSwitch },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{
targets: [1, 2],
searchable: true,
orderable: true
},
{
targets: [3],
searchable: true,
orderable: false
},
{
targets: [0,-1], // Targeting the last column (action column)
searchable: false,
orderable: false
},
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'Archive',
className: "btn btn-dark buttons-archive",
action: archiveAction,
init: function(api, node, config){
$(node).hide();
}
},
{
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = '/archive';
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
function renderResources(data, type, row) {
if (type === 'display' && row.resources) {
let html = '<ul>';
for (const [resource, actions] of Object.entries(row.resources)) {
html += `<li class="mb-1"><span class="badge badge-primary">${resource}</span>`;
for (const action of actions) {
html += `<span class="badge badge-secondary">${action}</span>`;
}
html += '</li>';
}
html += '</ul>';
return html;
} else if (type === 'display' && !row.resources) {
return '<span class="badge badge-danger">No Permission assigned</span>';
} else {
return '';
}
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" data-id="'+ row.id +'" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<ul class="table-controls">
<li>
<a href="${ editUrl.replace('0',row.id)}" class="bs-tooltip edit" data-bs-toggle="tooltip" data-bs-placement="top" title="Edit" data-bs-original-title="Edit" aria-label="Edit">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2 p-1 br-8 mb-1">
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
</svg>
</a>
</li>
</ul>`;
}
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
console.log("checkbox is checked", + checkedCount)
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle click event for view button
function viewClickEvent(dataTableInstance) {
$('body').on('click', '.view', function(){
var id =$(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,123 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Add Role and Assgin Permission</h3>
</div>
<div class="col text-end">
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
{% if form.non_field_errors %}
<div class="text-danger">
<ul>
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group mb-4">
<label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>
<input type="text" class="form-control" name="{{ form.name.name }}" id="{{ form.name.id_for_label }}" placeholder="Enter {{ form.name.label }}" value="{% if form.name.value %}{{ form.name.value }}{% endif %}">
<!-- Display field errors -->
{% for error in form.name.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
{% if form.active %}
<div class="form-group mb-3">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="{{ form.active.id_for_label }}" name="{{ form.active.name }}" {% if form.active.value %}checked{% endif %}>
<label class="form-check-label" for="{{ form.active.id_for_label }}">{{ form.active.label }}</label>
</div>
{% if form.active.errors %}
<div class="text-danger">{{ form.active.errors }}</div>
{% endif %}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
</div>
{% endif %}
<div class="table-responsive">
<label>Assigned Permission</label>
<table class="table">
<thead>
<tr>
<th>Resource</th>
<th class="text-center" colspan="4">Action</th>
</tr>
</thead>
<tbody>
<!-- Replace this section with your Django template code -->
{% for resource, actions in app_resource_action.items %}
<tr>
<td class="">{{ resource }}</td>
{% for id, action in actions.items %}
<td>
<div class="form-check form-check-success form-check-inline">
<input class="form-check-input mt-1" type="checkbox" name="app_resource_action" value="{{ id }}" id="checkbox_{{ id }}" {% if id in form.app_resource_action.value %}checked{% endif %}>
&nbsp;&nbsp;<label class="form-check-label pd-0" for="checkbox_{{ id }}">{{ action }}</label>
</div>
</td>
{% endfor %}
</tr>
{% endfor %}
<!-- End of Django template code -->
</tbody>
</table>
</div>
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
$(document).ready(function() {
$('.js-example-basic-multiple').select2({
placeholder: 'Select options',
allowClear: true,
tags: true, // Allow the user to enter custom tags
tokenSeparators: [',', ' '], // Customize token separators
closeOnSelect: false // Keep the dropdown open after selection
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,127 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Profile</h3>
</div>
<div class="col text-end">
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
{% comment %} <a class="btn btn-primary mb-2 me-4" href="{% url 'accounts:role_add' %}">Add Role</a> {% endcomment %}
</div>
</div>
<div class="row layout-spacing">
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12 layout-spacing">
<div class="user-profile">
<div class="widget-content widget-content-area">
<div class="d-flex justify-content-between">
<h3 class=""></h3>
<a href="{% url 'module_iam:profile_details_edit' %}" class="mt-2 edit-profile"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-3"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg></a>
</div>
<div class="mb-5">
<div class="row">
<div class="col-4 text-center">
{% if not data_obj.profile_photo%}
<img alt="avatar" src="{% static 'img/default_profile.jpg' %}" class="rounded-circle" style="height: 150px; width: 150px">
{% else %}
<img src="{{ data_obj.profile_photo.url }}" alt="avatar" class="rounded-circle" style="height: 150px; width: 150px">
{%endif%}
</div>
<div class="col-8 d-flex align-items-center">
<span class="material-symbols-outlined" style="font-size: 60px;">
person
</span>
<h1 class="">{{data_obj.first_name}} {{data_obj.last_name}}</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="card mb-4">
<div class="card-body">
<div class="row">
<div class="col-sm-3">
<p class="mb-0">Type</p>
</div>
<div class="col-sm-9">
<p class="text-muted mb-0">{{data_obj.principal_type.name}}</p>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-3">
<p class="mb-0">Phone</p>
</div>
<div class="col-sm-9">
<p class="text-muted mb-0">{{data_obj.phone_no}}</p>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-3">
<p class="mb-0">Gender</p>
</div>
<div class="col-sm-9">
<p class="text-muted mb-0">{{data_obj.gender}}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card mb-4">
<div class="card-body">
<div class="row">
<div class="col-sm-3">
<p class="mb-0">Email</p>
</div>
<div class="col-sm-9">
<p class="text-muted mb-0">{{data_obj.email}}</p>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-3">
<p class="mb-0">Date Of Birth</p>
</div>
<div class="col-sm-9">
<p class="text-muted mb-0">{{data_obj.date_of_birth}}</p>
</div>
</div>
<hr>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
<script>
</script>
{% endblock %}

View File

@@ -0,0 +1,136 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Profile</h3>
</div>
<div class="col text-end">
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button>
{% comment %} <a class="btn btn-primary mb-2 me-4" href="{% url 'accounts:role_add' %}">Add Role</a> {%endcomment %}
</div>
</div>
<div class="row layout-spacing">
<div class="col-xl-12 col-lg-12 col-md-12 layout-spacing">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<!-- <form class="section general-info">
<div class="info">
<h6 class="">Profile</h6>
<div class="row">
<div class="col-lg-11 mx-auto">
<div class="row">
<div class="col-xl-2 col-lg-12 col-md-4">
<div class="profile-image">
<div class="img-uploader-content">
<input type="file" class="filepond"
name="filepond" accept="image/png, image/jpeg"/>
</div>
</div>
</div>
<div class="col-xl-10 col-lg-12 col-md-8 mt-md-0 mt-4">
<div class="form">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="fullName">First Name</label>
<input type="text" class="form-control mb-3"
id="fullName" placeholder="Full Name"
value="Jimmy Turner">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="profession">Last Name</label>
<input type="text" class="form-control mb-3"
id="profession" placeholder="Designer"
value="Web Developer">
</div>
</div>
<div class="col-md-12 mt-1">
<div class="form-group text-end">
<button
class="btn btn-secondary _effect--ripple waves-effect waves-light">Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form> -->
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/filepond_cdn_js.html" %}
<script>
/**
* ==================
* Single File Upload
* ==================
*/
// We register the plugins required to do
// image previews, cropping, resizing, etc.
FilePond.registerPlugin(
FilePondPluginImagePreview,
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 %}";
if (profilePhotoUrl) {
// If the URL exists, add the profile photo to FilePond
pond.addFile(profilePhotoUrl);
}
</script>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<a class="d-flex align-items-center pl-2"onclick="history.back()">
<img class="" src="{% static 'src/assets/img/left-arrow.svg' %}" style="height: 20px;">
<h3 class="m-2">{{operation}} Notification</h3>
</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<form method="post">
{% csrf_token %}
{% include 'base_structure/includes/dynamic_template_form.html' with form=form %}
<div class="mt-4 mb-0">
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% endblock %}

View File

@@ -0,0 +1,362 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Push Notification</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add
Category</a> {% endcomment %}
<a class="btn btn-primary mb-2 me-4" href="{% url 'module_notification:notification_add' %}">Add</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending" style="width: 50.2656px;">
#</th>
<th class="sorting_asc text-center" tabindex="0" aria-controls="style-3"
aria-sort="ascending" style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">Title</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1" style="width: 44.2344px;">Message</th>
<th class="sorting text-center" tabindex="5" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
<th class="sorting text-center" tabindex="6" aria-controls="style-3"
style="width: 79.7969px;">Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance
var actionUrl = '{% url "module_notification:notification_action" %}'
var mainUrl = '{% url "module_notification:notification_list" %}?deleted_flag=False';
var editUrl = "{% url 'module_notification:notification_edit' pk=0 %}"
var viewArchiveUrl = "{% url 'module_notification:notification_action' %}"
var notifyUrl = "{% url 'module_notification:notification_send' %}"
// Entry point
$(document).ready(function() {
tableName = $('#table')
dataTableInstance = initializeDataTable(tableName, mainUrl);
activeSwitchEventListener();
sendNotificationAction();
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "title", className: "text-center" },
{ data: "message", className: "text-center" },
{ data: "active", className: "text-center", render: renderSwitch },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{ targets: [1, 2, 3], searchable: true, orderable: true },
{ targets: [0, -1], searchable: false, orderable: false }
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'Archive',
className: "btn btn-dark buttons-archive",
action: archiveAction,
init: function(api, node, config){
$(node).hide();
}
},
{
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = viewArchiveUrl;
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" id="form-custom-switch-checked' + row.id + '" data-id="' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<div class="dropdown">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink${row.id}" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item edit" href="${ editUrl.replace('0',row.id)}" data-id="${row.id}">Edit</a>
<a class="dropdown-item send" href="javascript:void(0);" data-id="${row.id}">Send</a>
</div>
</div>`;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
}
function sendNotificationAction() {
$('body').on('click', '.send', function() {
var id = $(this).closest('tr').find('.send').data('id');
console.log("=================================================id is", + id);
Swal.fire({
title: 'Send Notification?',
text: 'Once sent, the notification will be delivered to the user.',
icon: 'info',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, send it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform notification sending action
$.ajax({
url: notifyUrl, // Replace with your notification sending endpoint
type: 'POST',
data: {
id: id,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Notification Sent!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can perform any other actions after sending the notification
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,520 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Manage Contact Us</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="{% url 'module_cms:faq_add' %}">Add FAQ</a> {% endcomment %}
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="faqs_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="faqs" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Email Address</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Subject</th>
<th class="sorting text-center" tabindex="3" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Message</th>
<th class="sorting text-center" tabindex="4" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Reply</th>
<th class="sorting text-center" tabindex="5" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
<th class="sorting text-center" tabindex="6" aria-controls="style-3"
style="width: 79.7969px;">Action</th>
</tr>
</thead>
<thead id="filterboxrow">
<tr>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th rowspan="1" colspan="1">Email Address</th>
<th rowspan="1" colspan="1">Subject</th>
<th rowspan="1" colspan="1">Message</th>
<th rowspan="1" colspan="1">Reply</th>
<th class="invisible" rowspan="1" colspan="1">Active</th>
<th class="invisible" rowspan="1" colspan="1">Action</th>
</tr>
<thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Message/ reply modal -->
<div class="modal fade" id="tabsModalMessageReply" tabindex="-1" role="dialog" aria-labelledby="tabsModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" >
<div class="modal-header">
<h5 class="modal-title" id="tabsModalLabel">Message / Reply</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="mb-3">
<p class="mt-3" id="emailData"></p>
<p class="mt-3" id="subjectData"></p>
</div>
<div class="simple-pill">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-message-tab" data-bs-toggle="pill" data-bs-target="#pills-message" type="button" role="tab" aria-controls="pills-message" aria-selected="true">Message</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-reply-tab" data-bs-toggle="pill" data-bs-target="#pills-reply" type="button" role="tab" aria-controls="pills-reply" aria-selected="false">Reply</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-message" role="tabpanel" aria-labelledby="pills-message-tab" tabindex="0">
<div class="card">
<div class="card-body">
<p class="mt-3" id="messageData"></p>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-reply" role="tabpanel" aria-labelledby="pills-reply-tab" tabindex="0">
<div class="card">
<div class="card-body">
<p class="mt-3" id="replyData"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-light-dark" data-bs-dismiss="modal">Discard</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="replyModal" tabindex="-1" role="dialog" aria-labelledby="replyModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="replyModalLabel">Message / Reply</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"> </button>
</div>
<div class="modal-body">
<input type="hidden" id="replyId">
<div class="form-group mb-3">
<label for="recipient-name" class="form-label">Recipient:</label>
<p id="recipient-name"></p>
</div>
<div class="form-group mb-3">
<label for="message" class="form-label">Message:</label>
<p id="message"></p>
</div>
<div class="form-group">
<label for="reply-text" class="form-label">Reply:</label>
<textarea class="form-control" id="reply-text"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light-dark" data-bs-dismiss="modal">Discard</button>
<button type="button" class="btn btn-primary" id="submitReply">Submit</button>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_support:contact_us_list' %}?deleted_flag=False"
var actionUrl = "{% url 'module_support:contact_us_action' %}"
var viewArchiveUrl = "{% url 'module_support:contact_us_archive' %}"
var replyUrl = "{% url 'module_support:contact_us_reply' id=0 %}"
// Entry point
$(document).ready(function() {
tableName = $('#faqs');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance)
replyEvent(dataTableInstance)
activeSwitchEventListener()
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "email_address" },
{ data: "subject" },
{ data: "message" },
{ data: "reply" },
{ data: "active", className: "text-center", render: renderSwitch },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{
"targets": [3,4,5],
"render": function (data, type, row) {
// Adjust the length of text you want to show before truncating
var maxLength = 40;
// Truncate the text if it exceeds the maxLength
var truncatedText = data.length > maxLength ? data.substr(0, maxLength) + '...' : data;
// Return the truncated text
return truncatedText;
}
},
{
targets: [1, 2, 3],
searchable: true,
orderable: true
},
{
targets: [0,-1], // Targeting the last column (action column)
searchable: false,
orderable: false
},
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'Archive',
className: "btn btn-dark buttons-archive",
action: archiveAction,
init: function(api, node, config){
$(node).hide();
}
},
{
text: 'View Archive List',
className: "btn btn-dark ",
action: function () {
// Add your action here, e.g., redirect to archive page
window.location.href = viewArchiveUrl;
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render switch
function renderSwitch(data, type, row) {
var checkedAttribute = data.toLowerCase() === 'true' ? 'checked' : '';
var switchHTML = '<div class="switch form-switch-custom switch-inline form-switch-primary">';
switchHTML += '<input class="switch-input" type="checkbox" role="switch" data-id="'+ row.id +'" id="form-custom-switch-checked' + row.id + '" ' + checkedAttribute + '>';
switchHTML += '</div>';
return switchHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<div class="dropdown">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink${row.id}" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item view" href="javascript:void(0);" data-id="${row.id}">View</a>
<a class="dropdown-item reply" href="javascript:void(0)" data-id="${row.id}">Reply</a>
</div>
</div>`;
}
// Function to handle archive action
function archiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No users selected',
text: 'Please select at least one user to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "archive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add individual search inputs to the first row of the thead section
$('thead#filterboxrow th').each(function (index) {
var title = $(this).text();
var input = $('<input type="text" class="form-control" placeholder="Search ' + title + '"/>')
.on('keyup change', function () {
if (api.column(index).search() !== this.value) {
api.column(index).search(this.value).draw();
}
});
$(this).empty().append(input);
});
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var archiveButton = $('.buttons-archive');
console.log("checkbox is checked", + checkedCount)
archiveButton.toggle(checkedCount > 0);
});
}
// Function to handle click event for view button
function viewClickEvent(dataTableInstance) {
$('body').on('click', '.view', function(){
var id = $(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
// Populate the modal fields with the data
$('#messageData').text(rowData.message);
$('#emailData').text(rowData.email_address);
$('#subjectData').text(rowData.subject);
$('#replyData').text(rowData.reply);
// Show the modal
$('#tabsModalMessageReply').modal('show');
});
}
// Function to add event listener for switch
function activeSwitchEventListener() {
// Add event listener for switch change event
$('body').on('change', '.switch-input', function() {
var rowId = $(this).closest('tr').find('.switch-input').data('id');
var isActive = $(this).prop('checked');
console.log(rowId, isActive)
// Perform active toggle action for the current user
$.ajax({
url: actionUrl, // Replace with your active toggle endpoint
type: 'POST',
data: {
action: "active",
ids: [rowId],
active: isActive,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
}
function replyEvent(dataTableInstance){
$('body').on('click', '.reply', function(){
var id = $(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
$('#message').text(rowData.message);
$('#recipient-name').text(rowData.email_address);
$('#replyId').val(id);
$('#replyModal').modal('show');
$('#replyModal').on('click', '#submitReply', function(e) {
e.preventDefault();
var id = $('#replyId').val();
var replyMessage = $('#reply-text').val();
console.log(id, message)
// Call the AJAX request
$.ajax({
type: 'POST',
url: replyUrl.replace("0", id),
data: { message: replyMessage, csrfmiddlewaretoken: '{{csrf_token}}' },
success: function(response) {
console.log('Response from server:', response);
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Close the modal
$('#replyModal').modal('hide');
// Empty the input fields
$('#reply-text').val('');
// Reload the DataTable after successful toggle
reloadDataTable();
},
error: function(error) {
console.error('Error:', error);
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
});
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,375 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Archive Contact Us</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="{% url 'module_cms:faq_add' %}">Add FAQ</a> {% endcomment %}
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="faqs_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="faqs" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Email Address</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Subject</th>
<th class="sorting text-center" tabindex="3" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Message</th>
<th class="sorting text-center" tabindex="4" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Reply</th>
<th class="sorting text-center" tabindex="6" aria-controls="style-3"
style="width: 79.7969px;">Action</th>
</tr>
</thead>
<thead id="filterboxrow">
<tr>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th class="text-center" rowspan="1" colspan="1">id</th>
<th rowspan="1" colspan="1">Email Address</th>
<th rowspan="1" colspan="1">Subject</th>
<th rowspan="1" colspan="1">Message</th>
<th rowspan="1" colspan="1">Reply</th>
<th class="invisible" rowspan="1" colspan="1">Action</th>
</tr>
<thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Message/ reply modal -->
<div class="modal fade" id="tabsModalMessageReply" tabindex="-1" role="dialog" aria-labelledby="tabsModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" >
<div class="modal-header">
<h5 class="modal-title" id="tabsModalLabel">Message / Reply</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="mb-3">
<p class="mt-3" id="emailData"></p>
<p class="mt-3" id="subjectData"></p>
</div>
<div class="simple-pill">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-message-tab" data-bs-toggle="pill" data-bs-target="#pills-message" type="button" role="tab" aria-controls="pills-message" aria-selected="true">Message</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-reply-tab" data-bs-toggle="pill" data-bs-target="#pills-reply" type="button" role="tab" aria-controls="pills-reply" aria-selected="false">Reply</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-message" role="tabpanel" aria-labelledby="pills-message-tab" tabindex="0">
<div class="card">
<div class="card-body">
<p class="mt-3" id="messageData"></p>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-reply" role="tabpanel" aria-labelledby="pills-reply-tab" tabindex="0">
<div class="card">
<div class="card-body">
<p class="mt-3" id="replyData"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-light-dark" data-bs-dismiss="modal">Discard</button>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_support:contact_us_list' %}?deleted_flag=True"
var actionUrl = "{% url 'module_support:contact_us_action' %}"
// Entry point
$(document).ready(function() {
tableName = $('#faqs');
dataTableInstance = initializeDataTable(tableName, mainUrl);
viewClickEvent(dataTableInstance)
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: null, className: "text-center", render: renderCheckbox },
{ data: "id", className: "text-center" },
{ data: "email_address" },
{ data: "subject" },
{ data: "message" },
{ data: "reply" },
{ data: null, className: "text-center", render: renderActions }
],
debug: true,
columnDefs: [
{
"targets": [3,4,5],
"render": function (data, type, row) {
// Adjust the length of text you want to show before truncating
var maxLength = 40;
// Truncate the text if it exceeds the maxLength
var truncatedText = data.length > maxLength ? data.substr(0, maxLength) + '...' : data;
// Return the truncated text
return truncatedText;
}
},
{
targets: [1, 2, 3],
searchable: true,
orderable: true
},
{
targets: [0,-1], // Targeting the last column (action column)
searchable: false,
orderable: false
},
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
{
text: 'UnArchive',
className: "btn btn-dark buttons-unarchive",
action: unArchiveAction,
init: function(api, node, config){
$(node).hide();
}
}
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10,
initComplete: initCompleteCallback
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
// Render checkbox
function renderCheckbox(data, type, row) {
var checkboxHTML = '<div class="form-check form-check-danger">';
checkboxHTML += '<input class="form-check-input archive-checkbox" type="checkbox" value="' + row.id + '" data-id="'+ row.id +'" id="checkbox-' + row.id + '">';
checkboxHTML += '</div>';
return checkboxHTML;
}
// Render actions
function renderActions(data, type, row) {
return `
<div class="dropdown">
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink${row.id}" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink${row.id}" style="">
<a class="dropdown-item view" href="javascript:void(0);" data-id="${row.id}">View</a>
</div>
</div>`;
}
// Callback function for DataTable initialization complete event
function initCompleteCallback() {
var api = this.api();
// Add individual search inputs to the first row of the thead section
$('thead#filterboxrow th').each(function (index) {
var title = $(this).text();
var input = $('<input type="text" class="form-control" placeholder="Search ' + title + '"/>')
.on('keyup change', function () {
if (api.column(index).search() !== this.value) {
api.column(index).search(this.value).draw();
}
});
$(this).empty().append(input);
});
// Add event listener for checkbox change
$('body').on('change', 'input[type="checkbox"]', function () {
var checkedCount = $('tbody input.archive-checkbox:checked').length;
var unarchiveButton = $('.buttons-unarchive');
console.log("checkbox is checked", + checkedCount)
unarchiveButton.toggle(checkedCount > 0);
});
}
// Function to handle click event for view button
function viewClickEvent(dataTableInstance) {
$('body').on('click', '.view', function(){
var id = $(this).data('id');
var rowData = dataTableInstance.row($(this).closest('tr')).data();
// Populate the modal fields with the data
$('#messageData').text(rowData.message);
$('#emailData').text(rowData.email_address);
$('#subjectData').text(rowData.subject);
$('#replyData').text(rowData.reply);
// Show the modal
$('#tabsModalMessageReply').modal('show');
});
}
// Function to handle archive action
function unArchiveAction() {
// Get all the checked checkboxes
var checkedCheckboxes = $('.archive-checkbox:checked');
// If no checkboxes are checked, show an error message
if (checkedCheckboxes.length === 0) {
Swal.fire({
title: 'No record selected',
text: 'Please select at least one record to archive.',
icon: 'error',
showConfirmButton: true
});
return;
}
// Get the IDs of the checked checkboxes
var ids = checkedCheckboxes.map(function() {
return $(this).val();
}).get();
// Perform archive action with the collected user IDs
Swal.fire({
title: 'Are you sure?',
text: 'Once archived, you will recover it from archive list!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, archive it!'
}).then((result) => {
if (result.isConfirmed) {
// Perform archive action
$.ajax({
url: actionUrl, // Replace with your archive endpoint
type: 'POST',
data: {
action: "unarchive",
ids: ids,
csrfmiddlewaretoken: '{{csrf_token}}'
},
success: function(response) {
// Show success message
Swal.fire({
title: 'Done!',
text: response.msg,
icon: 'success',
showConfirmButton: true
});
// Optionally, you can reload the DataTable after successful archive
reloadDataTable();
},
error: function(response) {
// Show error message
Swal.fire({
title: 'Error!',
text: response.message,
icon: 'error',
showConfirmButton: true
});
}
});
}
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,208 @@
{% extends 'base_structure/layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% include "cdn_through_html/animate_cdn_css.html" %}
{% include "cdn_through_html/modal_cdn_css.html" %}
{% include "cdn_through_html/switches_cdn_css.html" %}
{% include "cdn_through_html/sweetalert2_cdn_css.html" %}
{% endblock %}
{% block content %}
<div class="row layout-top-spacing">
<div class="col-lg-12">
<div class="row mb-2">
<div class="col">
<h3>Manage Feedback</h3>
</div>
<div class="col text-end">
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
<i class="fa fa-arrow-left"></i>
Back
</button> {% endcomment %}
{% comment %} <a class="btn btn-success mb-2 me-4" href="{% url 'module_cms:faq_category_add' %}">Add Category</a> {% endcomment %}
{% comment %} <a class="btn btn-primary mb-2 me-4" href="{% url 'module_cms:faq_add' %}">Add FAQ</a> {% endcomment %}
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
<div class="widget-content widget-content-area">
<div id="table_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
<div class="table-responsive">
<table id="table" class="table style-3 dt-table-hover dataTable" role="grid"
aria-describedby="style-3_info">
<thead>
<tr role="row">
<th class="checkbox-column sorting_asc text-center" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 50.2656px;">#</th>
<th class="sorting text-center" tabindex="1" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Email Address</th>
<th class="sorting text-center" tabindex="2" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Subject</th>
<th class="sorting text-center" tabindex="3" aria-controls="style-3"
colspan="1"
style="width: 44.2344px;">Message</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Message/ reply modal -->
<div class="modal fade" id="tabsModalMessageReply" tabindex="-1" role="dialog" aria-labelledby="tabsModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" >
<div class="modal-header">
<h5 class="modal-title" id="tabsModalLabel">Message / Reply</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<div class="mb-3">
<p class="mt-3" id="emailData"></p>
<p class="mt-3" id="subjectData"></p>
</div>
<div class="simple-pill">
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-message-tab" data-bs-toggle="pill" data-bs-target="#pills-message" type="button" role="tab" aria-controls="pills-message" aria-selected="true">Message</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-reply-tab" data-bs-toggle="pill" data-bs-target="#pills-reply" type="button" role="tab" aria-controls="pills-reply" aria-selected="false">Reply</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-message" role="tabpanel" aria-labelledby="pills-message-tab" tabindex="0">
<div class="card">
<div class="card-body">
<p class="mt-3" id="messageData"></p>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-reply" role="tabpanel" aria-labelledby="pills-reply-tab" tabindex="0">
<div class="card">
<div class="card-body">
<p class="mt-3" id="replyData"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-light-dark" data-bs-dismiss="modal">Discard</button>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
{% include "cdn_through_html/datatable_button_cdn_js.html" %}
{% include "cdn_through_html/sweetalert2_cdn_js.html" %}
<script>
// Define DataTable instance
var dataTableInstance;
var mainUrl = "{% url 'module_support:feedback_list' %}?deleted_flag=False"
// Entry point
$(document).ready(function() {
tableName = $('#table');
dataTableInstance = initializeDataTable(tableName, mainUrl);
});
// Function to initialize DataTable
function initializeDataTable(tableName, mainUrl) {
return tableName.DataTable({
processing: true,
serverSide: true,
ajax: {
url: mainUrl,
type: "GET",
},
columns: [
{ data: "id", className: "text-center" },
{ data: "principal" },
{ data: "feedback_reaction" },
{ data: "comment" },
],
debug: true,
columnDefs: [
{
"targets": [3],
"render": function (data, type, row) {
// Adjust the length of text you want to show before truncating
var maxLength = 40;
// Truncate the text if it exceeds the maxLength
var truncatedText = data.length > maxLength ? data.substr(0, maxLength) + '...' : data;
// Return the truncated text
return truncatedText;
}
},
{
targets: [1, 2, 3],
searchable: true,
orderable: true
}
],
dom: "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'Bf>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
buttons: [
],
oLanguage: {
oPaginate: { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
sInfo: "Showing page _PAGE_ of _PAGES_",
sSearch: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
sSearchPlaceholder: "Search...",
sLengthMenu: " _MENU_",
},
stripeClasses: [],
lengthMenu: [5, 10, 20, 50],
pageLength: 10
});
}
// Function to reload the DataTable
function reloadDataTable() {
dataTableInstance.ajax.reload();
}
</script>
{% endblock %}