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