From 27168352abcf1d6689cdf8267e387527dc86e459 Mon Sep 17 00:00:00 2001 From: bobbyvish Date: Mon, 18 Mar 2024 11:44:06 +0530 Subject: [PATCH] refactore iam principal and password reset --- module_auth/api/utils.py | 2 +- module_auth/api/views.py | 21 +- module_auth/urls.py | 4 +- module_iam/forms.py | 50 ++-- module_iam/urls.py | 11 +- module_iam/views.py | 254 +++++++++++++--- module_notification/views.py | 10 +- module_project/constants.py | 1 + module_project/settings/base.py | 19 +- module_project/settings/staging.py | 6 +- module_project/settings_file.py | 123 -------- module_project/urls.py | 4 +- module_project/wsgi.py | 5 + .../includes/dynamic_template_form.html | 3 +- .../layout/base_authentication_template.html | 2 +- templates/module_auth/email_template.html | 2 +- templates/module_iam/iam_group.html | 4 +- templates/module_iam/iam_principal_add.html | 48 +++ .../module_iam/iam_principal_group_link.html | 279 +++++++++++++----- .../iam_principal_group_link_edit.html | 64 ++++ 20 files changed, 594 insertions(+), 318 deletions(-) delete mode 100644 module_project/settings_file.py create mode 100644 templates/module_iam/iam_principal_add.html create mode 100644 templates/module_iam/iam_principal_group_link_edit.html diff --git a/module_auth/api/utils.py b/module_auth/api/utils.py index 4818bce..2386d5f 100644 --- a/module_auth/api/utils.py +++ b/module_auth/api/utils.py @@ -155,7 +155,7 @@ def authticate_with_otp_and_passsword(principal: IAmPrincipal, otp=None, passwor if otp: otp_instance = IAmPrincipalOtp.objects.filter( - principal=principal, otp_code=otp + principal=principal, otp_code=otp, is_used=False ).last() if not otp_instance: diff --git a/module_auth/api/views.py b/module_auth/api/views.py index 7a5a7e2..cb6b8b0 100644 --- a/module_auth/api/views.py +++ b/module_auth/api/views.py @@ -137,14 +137,14 @@ class OtpRequestView(APIView): # principal = auth_service.get_principal_by_email(request.data.get("email")) otp_code = SMSService().create_otp( - principal=principal, otp_purpose="Forget password" + principal=principal, otp_purpose="Password Reset Request" ) except Exception as e: return ApiResponse.error(message=str(e), errors=str(e)) email_service = EmailService( - subject="Forget Password", + subject="Password Reset Request", to=principal.email, from_email=settings.EMAIL_HOST_USER, ) @@ -197,24 +197,27 @@ class OTPVerificationView(APIView): class ForgetPasswordView(APIView): - authentication_classes = [JWTAuthentication] - permission_classes = [IsAuthenticated] + authentication_classes = [] + permission_classes = [] serializer_class = PasswordResetSerializer def post(self, request): email = request.data.get("email") - + print("email for password reset", email) principal = get_principal_by_email(email=email) - otp_instance = IAmPrincipalOtp.objects.filter(principal=principal).last() + if isinstance(principal, Response): + return principal + + otp_instance = IAmPrincipalOtp.objects.filter(principal=principal, is_used=True).last() if not otp_instance: - return ApiResponse.error(message=constants.SESSION_EXPIRED) + return ApiResponse.error(message=constants.PASSWORD_RESET_SESSION_EXPIRE) if otp_instance.is_expired(): - return ApiResponse.error(message=constants.SESSION_EXPIRED) + return ApiResponse.error(message=constants.PASSWORD_RESET_SESSION_EXPIRE) - serializer = self.serializer_class(request.user, data=request.data) + serializer = self.serializer_class(principal, data=request.data) if not serializer.is_valid(): error_response = { "status": status.HTTP_403_FORBIDDEN, diff --git a/module_auth/urls.py b/module_auth/urls.py index 8deb491..23d6bf6 100644 --- a/module_auth/urls.py +++ b/module_auth/urls.py @@ -1,10 +1,12 @@ from django.urls import path from . import views -from django.views.generic import TemplateView +from django.views.generic import TemplateView, RedirectView app_name = "module_auth" urlpatterns = [ + # redirect to different url + path('', RedirectView.as_view(url='login'), name='index'), path('login/', views.AdminLoginView.as_view(), name="login"), path('logout/', views.AdminLogoutView.as_view(), name="logout"), path('password-reset/', views.CustomPasswordResetView.as_view(), name='password_reset'), diff --git a/module_iam/forms.py b/module_iam/forms.py index 8ecffc7..9509b30 100644 --- a/module_iam/forms.py +++ b/module_iam/forms.py @@ -55,13 +55,6 @@ class IAmPrincipalForm(forms.ModelForm): 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 = [ @@ -71,26 +64,19 @@ class IAmPrincipalForm(forms.ModelForm): "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 + active=True, deleted=False, name__in=(PRINCIPAL_TYPE_ADMIN, PRINCIPAL_TYPE_SUBADMIN) ) # 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 + if instance is not None and instance.pk is not None: 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) @@ -237,21 +223,20 @@ class ProfileEditForm(forms.ModelForm): ] -class IAmPrincipalGroupLinkForm(forms.ModelForm): +class IAmPrincipalGroupLinkForm(IAmPrincipalForm): class Meta: model = models.IAmPrincipal fields = [ - # "principal_type", + "principal_type", + "first_name", + "last_name", "email", + "password", + "confirm_password", "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), @@ -261,13 +246,18 @@ class IAmPrincipalGroupLinkForm(forms.ModelForm): ), ) - 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 - + def save(self, commit=True): + # First, save the instance of the IAmPrincipal model as usual + principal = super().save(commit=False) + # If the principal_group field has data + if self.cleaned_data['principal_group']: + # Get the principal_group data + principal_group_data = self.cleaned_data['principal_group'] + # Update the many-to-many relationship + principal.principal_group.set(principal_group_data) + # Save the instance to the database + if commit: + principal.save() class IAmPrincipalTypeForm(forms.ModelForm): class Meta: diff --git a/module_iam/urls.py b/module_iam/urls.py index 1aae51e..c347256 100644 --- a/module_iam/urls.py +++ b/module_iam/urls.py @@ -9,19 +9,20 @@ urlpatterns = [ # path('principal/', views.PrincipalListView.as_view(), name="principal_list"), # path('principal/add/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_add"), - # path('principal/edit/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_edit"), + path('principal/edit/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_edit"), # path('principal/delete/', 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//', views.PrincipalGroupLinkEditView.as_view(), name="principal_group_link_edit"), - + path('principal/group/link/list/admin/', views.PrincipalGroupLinkAdminListJsonView.as_view(), name="principal_group_link_list"), + path('principal/group/link/list/subadmin/', views.PrincipalGroupLinkSubAdminListJsonView.as_view(), name="principal_group_link_list_sub"), + path('principal/group/link/edit//', views.PrincipalGroupLinkEditView.as_view(), name="principal_group_link_edit"), + path('principal/group/link/action/', views.PrincipalGroupLinkActionView.as_view(), name="principal_group_link_action"), 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//', views.PrincipalGroupCreateOrUpdateView.as_view(), name="principal_group_edit"), - path('principal/group/action//', views.PrincipalGroupActionView.as_view(), name="principal_group_action"), + 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"), diff --git a/module_iam/views.py b/module_iam/views.py index 4641cf1..0fc0dba 100644 --- a/module_iam/views.py +++ b/module_iam/views.py @@ -1,9 +1,13 @@ from typing import Any + +from django.db import transaction from django.db.models.base import Model as Model from django.db.models.query import QuerySet from django.views import generic import logging +from datetime import datetime + from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q @@ -14,13 +18,14 @@ 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 module_project.utils import JsonResponseUtil from .forms import ( CustomAuthenticationForm, IAmPrincipalForm, IAmPrincipalGroupRoleLinkForm, IAmPrincipalRoleAppResourceActionLinkForm, IAmPrincipalGroupLinkForm, - ProfileEditForm + ProfileEditForm, ) from .models import ( IAmPrincipal, @@ -36,6 +41,7 @@ logger = logging.getLogger(__name__) # Create your views here. + class DashboardView(generic.TemplateView): page_name = iam_constant.RESOURCE_MANAGE_DASHBOARD template_name = "base_structure/layout/dashboard.html" @@ -51,11 +57,68 @@ class DashboardView(generic.TemplateView): 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 + context["active_user_count"] = active_user_count + context["total_user_count"] = total_user_count + context["page_name"] = self.page_name return context + +class PrincipalCreateOrUpdateView(LoginRequiredMixin, generic.View): + page_name = iam_constant.RESOURCE_IAM_PRINCIPAL + model = IAmPrincipal + form_class = IAmPrincipalForm + template_name = "module_iam/iam_principal_add.html" + success_url = reverse_lazy("module_iam:principal_group_link") + 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.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 PrincipalGroupLinkView(LoginRequiredMixin, generic.TemplateView): page_name = iam_constant.RESOURCE_IAM_PRINCIPAL_GROUP model = IAmPrincipal @@ -66,14 +129,121 @@ class PrincipalGroupLinkView(LoginRequiredMixin, generic.TemplateView): 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"] + columns = ["id", "first_name", "email", "is_active"] + order_columns = ["id", "first_name", "email", "is_active"] 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) + deleted_flag = self.request.GET.get("deleted_flag", False) + return self.model.objects.filter(deleted=deleted_flag, principal_type__name=iam_constant.PRINCIPAL_TYPE_ADMIN) + + def render_column(self, row, column): + if column == "principal_type_name": + return row.principal_type.name if row.principal_type else None + return super().render_column(row, column) + + 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(first_name__icontains=search_value) | Q(email__icontains=search_value) + ) + return qs + + +class PrincipalGroupLinkSubAdminListJsonView(BaseDatatableView): + model = IAmPrincipal + columns = ["id", "first_name", "email", "is_active"] + order_columns = ["id", "first_name", "email", "is_active"] + + def get_initial_queryset(self): + deleted_flag = self.request.GET.get("deleted_flag", False) + return self.model.objects.filter(deleted=deleted_flag, principal_type__name=iam_constant.PRINCIPAL_TYPE_SUBADMIN) + + def render_column(self, row, column): + if column == "principal_type_name": + return row.principal_type.name if row.principal_type else None + if column == "groups": + return [{"name": group.name} for group in row.principal_group.all()] + return super().render_column(row, column) + + 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(first_name__icontains=search_value) | Q(email__icontains=search_value) + ) + return qs + + +class PrincipalGroupLinkEditView(LoginRequiredMixin, generic.View): + page_name = iam_constant.RESOURCE_IAM_PRINCIPAL_GROUP + model = IAmPrincipal + template_name = "module_iam/iam_principal_group_link_edit.html" + form_class = IAmPrincipalGroupLinkForm + success_url = reverse_lazy("module_iam:principal_group_link") + success_message = "Record Updated 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", + } + 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(): + context = self.get_context_data(form=form) + return render(request, self.template_name, context=context) + form.save() + messages.success(request, self.success_message) + return redirect(self.success_url) + + +class PrincipalGroupLinkActionView(generic.View): + model = IAmPrincipal + + 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, 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 PrincipalGroupView(LoginRequiredMixin, generic.TemplateView): @@ -86,15 +256,6 @@ class PrincipalGroupView(LoginRequiredMixin, generic.TemplateView): 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 @@ -102,41 +263,43 @@ class PrincipalGroupListJsonView(BaseDatatableView): order_columns = ["id", "name", "active"] def get_initial_queryset(self): - deleted_flag = self.request.GET.get('deleted_flag', False) + 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) + 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()] + 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 - }) + 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' + 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" @@ -147,7 +310,9 @@ class PrincipalGroupCreateOrUpdateView(LoginRequiredMixin, generic.View): 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 + self.success_message = ( + constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED + ) return self.success_message def get_object(self): @@ -194,13 +359,14 @@ class AppRoleView(LoginRequiredMixin, generic.TemplateView): 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) + deleted_flag = self.request.GET.get("deleted_flag", False) return ( super(AppRoleListJsonView, self) .get_initial_queryset() @@ -247,10 +413,10 @@ class AppRoleListJsonView(BaseDatatableView): 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' + context["recordsTotal"] = len(role_data) + context["recordsFiltered"] = len(role_data) + context["data"] = role_data + context["result"] = "ok" return context @@ -319,7 +485,10 @@ class PrincipalProfileView(LoginRequiredMixin, generic.TemplateView): 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) + 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) @@ -327,6 +496,7 @@ class PrincipalProfileView(LoginRequiredMixin, generic.TemplateView): context["data_obj"] = self.get_object() return context + class PrincipalProfileEditView(generic.View): page_name = iam_constant.RESOURCE_MANAGE_DASHBOARD model = IAmPrincipal @@ -349,7 +519,7 @@ class PrincipalProfileEditView(generic.View): context = { # "page_name": self.page_name, "operation": "Edit", - "page_name": self.page_name + "page_name": self.page_name, } context.update(kwargs) # Include any additional context data passed to the view return context @@ -368,7 +538,7 @@ class PrincipalProfileEditView(generic.View): 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) \ No newline at end of file + return redirect(self.success_url) diff --git a/module_notification/views.py b/module_notification/views.py index 73cacf5..017bc31 100644 --- a/module_notification/views.py +++ b/module_notification/views.py @@ -129,6 +129,12 @@ class NotificationActionView(ActionMixin): class NotificationSendView(generic.View): model = PushNotification + 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 "" + def post(self, request, *args, **kwargs): id = request.POST.get("id") obj = self.model.objects.filter(pk=int(id)).first() @@ -141,8 +147,6 @@ class NotificationSendView(generic.View): 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( @@ -151,9 +155,7 @@ class NotificationSendView(generic.View): # 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, diff --git a/module_project/constants.py b/module_project/constants.py index 0616887..5c8ca9d 100644 --- a/module_project/constants.py +++ b/module_project/constants.py @@ -31,6 +31,7 @@ LOGIN_REQUIRED = "Login required to perform this action." LOGIN_SUCCESS = "Login successful." LOGOUT_SUCCESS = "Logout successful." SESSION_EXPIRED = "Your session has expired. Please log in again." +PASSWORD_RESET_SESSION_EXPIRE = "Password reset session has expired" ACCOUNT_DEACTIVATED = "Your account is inactive. Please contact support." EMAIL_EXISTS = "This email address is already in use. Please use a different email." INVALID_EMAIL_PASSWORD = "Invalid email or password." diff --git a/module_project/settings/base.py b/module_project/settings/base.py index 9a9744b..93bd6b5 100644 --- a/module_project/settings/base.py +++ b/module_project/settings/base.py @@ -28,7 +28,7 @@ if READ_DOT_ENV_FILE: # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -# SECRET_KEY = 'django-insecure-#7rdu=fr58ba9_!n3$l5pm!xs8l%6%8xt@vb8$&o@hqhd@rtd%' + SECRET_KEY = env.str("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! @@ -108,7 +108,7 @@ WSGI_APPLICATION = 'module_project.wsgi.application' DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", - "NAME": "digest_db", + "NAME": env.str("DB_DATABASE"), "HOST": env.str("DB_HOST"), "USER": env.str("DB_USERNAME"), "PASSWORD": env.str("DB_PASSWORD"), @@ -154,12 +154,12 @@ SHORT_DATE_FORMAT = "d-m-Y" TIME_FORMAT = "H:i p" # otp expire time limit -OTP_EXPIRE_TIME = 5 # mins +OTP_EXPIRE_TIME = 2 # mins APPEND_SLASH = True LOGIN_REDIRECT_URL = "/iam/dashboard/" -LOGIN_URL = "/auth/login/" -LOGOUT_REDIRECT_URL = "/auth/login/" +LOGIN_URL = "/login/" +LOGOUT_REDIRECT_URL = "/login/" # https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#substituting-a-custom-user-model @@ -219,8 +219,6 @@ 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 @@ -260,7 +258,7 @@ LOGGING = { # jwt configuration # https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html#settings SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": datetime.timedelta(days=20), + "ACCESS_TOKEN_LIFETIME": datetime.timedelta(minutes=5), "REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=30), "ROTATE_REFRESH_TOKENS": False, "BLACKLIST_AFTER_ROTATION": False, @@ -283,8 +281,3 @@ SIMPLE_JWT = { "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", "JTI_CLAIM": "jti", } - - -SOCIAL_AUTH_APPLE_CLIENT_ID = '' -SOCIAL_AUTH_APPLE_CLIENT_SECRET = '' -SOCIAL_AUTH_APPLE_REDIRECT_URI = '' \ No newline at end of file diff --git a/module_project/settings/staging.py b/module_project/settings/staging.py index fde3503..cbf8c35 100644 --- a/module_project/settings/staging.py +++ b/module_project/settings/staging.py @@ -5,14 +5,14 @@ import colorlog from logging.handlers import TimedRotatingFileHandler DEBUG = False -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["staging.eatwithdigest.com"] # CORS_ALLOWED_ORIGINS = [ # "http://127.0.0.1:3000", # ] -# CORS_ORIGIN_ALLOW_ALL = True +CORS_ORIGIN_ALLOW_ALL = True # CORS_ORIGIN_WHITELIST = ("http://localhost:3000",) @@ -69,7 +69,7 @@ LOGGING = { }, } -BASE_DOMAIN = "" +BASE_DOMAIN = "https://staging.eatwithdigest.com" # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ diff --git a/module_project/settings_file.py b/module_project/settings_file.py deleted file mode 100644 index 4fbbf66..0000000 --- a/module_project/settings_file.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Django settings for module_project project. - -Generated by 'django-admin startproject' using Django 4.2.5. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.2/ref/settings/ -""" - -from pathlib import Path - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-#7rdu=fr58ba9_!n3$l5pm!xs8l%6%8xt@vb8$&o@hqhd@rtd%' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'module_project.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'module_project.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/4.2/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - - -# Password validation -# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/4.2/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.2/howto/static-files/ - -STATIC_URL = 'static/' - -# Default primary key field type -# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/module_project/urls.py b/module_project/urls.py index b322818..09aa08c 100644 --- a/module_project/urls.py +++ b/module_project/urls.py @@ -23,8 +23,8 @@ urlpatterns = [ path('admin/', admin.site.urls), path('iam/', include('module_iam.urls')), - - path('auth/', include('module_auth.urls')), + + path('', include('module_auth.urls')), path('api/auth/', include('module_auth.api.urls')), path('cms/', include('module_cms.urls')), diff --git a/module_project/wsgi.py b/module_project/wsgi.py index 713dee0..016ce1a 100644 --- a/module_project/wsgi.py +++ b/module_project/wsgi.py @@ -8,6 +8,11 @@ https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ """ import os +import sys + +sys.path.append('/var/www/testing_django/testing') +sys.path.append('/var/www/testing_django/testing/testing') +sys.path.append('/var/www/testing_django/testing/venv/lib/python3.11/site-packages') from django.core.wsgi import get_wsgi_application diff --git a/templates/base_structure/includes/dynamic_template_form.html b/templates/base_structure/includes/dynamic_template_form.html index 060eaee..280bbde 100644 --- a/templates/base_structure/includes/dynamic_template_form.html +++ b/templates/base_structure/includes/dynamic_template_form.html @@ -30,6 +30,7 @@ {{ field.help_text }} {% endif %} + {% elif field.field.widget.input_type == 'checkbox' %}
@@ -67,4 +68,4 @@
{% endif %} {% endfor %} -
+ \ No newline at end of file diff --git a/templates/base_structure/layout/base_authentication_template.html b/templates/base_structure/layout/base_authentication_template.html index 4eaf075..0855b92 100644 --- a/templates/base_structure/layout/base_authentication_template.html +++ b/templates/base_structure/layout/base_authentication_template.html @@ -4,7 +4,7 @@ - Nifty11 + Digest {% load static %} diff --git a/templates/module_auth/email_template.html b/templates/module_auth/email_template.html index 4f2e214..a925ff4 100644 --- a/templates/module_auth/email_template.html +++ b/templates/module_auth/email_template.html @@ -28,7 +28,7 @@

{{ code }}

If you didn't request a password reset, you can safely ignore this email.

Thank you,

-

The Support Team

+

Team Digest

\ No newline at end of file diff --git a/templates/module_iam/iam_group.html b/templates/module_iam/iam_group.html index cdf271f..7166594 100644 --- a/templates/module_iam/iam_group.html +++ b/templates/module_iam/iam_group.html @@ -173,8 +173,8 @@ function initializeDataTable(tableName, mainUrl) { function renderRole(data, type, row) { if (type === 'display' && row.roles) { let html = '
    '; - for (const [name] of Object.entries(row.roles)) { - html += `
  • ${name}
  • `; + for (const [index, role] of Object.entries(row.roles)) { + html += `
  • ${role.name}
  • `; } html += '
'; return html; diff --git a/templates/module_iam/iam_principal_add.html b/templates/module_iam/iam_principal_add.html new file mode 100644 index 0000000..b73c21c --- /dev/null +++ b/templates/module_iam/iam_principal_add.html @@ -0,0 +1,48 @@ +{% extends 'base_structure/layout/base_template.html' %} +{% load static %} +{% block stylesheet %} + +{% endblock %} + +{% block content %} + +
+
+
+
+

{{operation}} Principal

+
+
+ +
+
+
+
+
+
+ +
+ {% csrf_token %} + {% include 'base_structure/includes/dynamic_template_form.html' with form=form %} +
+
+
+
+ +
+
+
+
+
+
+ + +{% endblock content %} + +{% block javascript %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/module_iam/iam_principal_group_link.html b/templates/module_iam/iam_principal_group_link.html index f5d4bc2..8f1f1f9 100644 --- a/templates/module_iam/iam_principal_group_link.html +++ b/templates/module_iam/iam_principal_group_link.html @@ -56,21 +56,33 @@ - # - Time - Meal - Medication - Bowel Movement - Symptoms + Id + Id + Name + Email + Permission + Pricipal_type + Active + Action @@ -100,17 +112,30 @@ Record Id - Name - Email - Permission - Principal Type - Id + Id + Name + Email + Groups + Pricipal_type + Active + Action @@ -153,26 +178,42 @@ // 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' %}" +var adminMainUrl = "{% url 'module_iam:principal_group_link_list' %}?deleted_flag=False" +var editUrl = "{% url 'module_iam:principal_edit' pk=0 %}" +var actionUrl = "{% url 'module_iam:principal_group_link_action' %}" + // Entry point $(document).ready(function() { + + const table1Settings = { + tableId: '#table', + MainUrl: "{% url 'module_iam:principal_group_link_list' %}?deleted_flag=False", + actionUrl: "{% url 'module_iam:principal_group_link_action' %}", + editUrl: "{% url 'module_iam:principal_edit' pk=0 %}" + }; - tableName = $('#table'); - dataTableInstance = initializeDataTable(tableName, mainUrl); - viewClickEvent(dataTableInstance) - activeSwitchEventListener() + const table2Settings = { + tableId: '#table2', + MainUrl: "{% url 'module_iam:principal_group_link_list_sub' %}?deleted_flag=False", + actionUrl: "{% url 'module_iam:principal_group_link_action' %}", + editUrl: "{% url 'module_iam:principal_group_link_edit' pk=0 %}" + }; + + dataTableInstance = initializeDataTable(table1Settings); + activeSwitchEventListener(dataTableInstance); + + dataTable2Instance = initialize2DataTable(table2Settings); + activeSwitchEventListener(dataTable2Instance); }); // Function to initialize DataTable -function initializeDataTable(tableName, mainUrl) { - return tableName.DataTable({ +function initializeDataTable(tableSettings) { + return $(tableSettings.tableId).DataTable({ processing: true, serverSide: true, ajax: { - url: mainUrl, + url: tableSettings.MainUrl, type: "GET", }, columns: [ @@ -186,9 +227,13 @@ function initializeDataTable(tableName, mainUrl) { return `
    All Access Permission
` } }, - { data: "principal_type__name", className: "text-center"}, + { data: "principal_type_name", className: "text-center" }, { data: "is_active", className: "text-center", render: renderSwitch }, - { data: null, className: "text-center", render: renderActions } + { data: null, className: "text-center", + render: (data, type, row) => { + return renderActions(data, type, row, tableSettings.editUrl); + } + } ], debug: true, columnDefs: [ @@ -215,7 +260,9 @@ function initializeDataTable(tableName, mainUrl) { { text: 'Archive', className: "btn btn-dark buttons-archive", - action: archiveAction, + action: function (e, dt, node, config) { + archiveAction(e, dt, node, config, tableSettings); + }, init: function(api, node, config){ $(node).hide(); } @@ -243,28 +290,106 @@ function initializeDataTable(tableName, mainUrl) { }); } -function renderResources(data, type, row) { - if (type === 'display' && row.resources) { - let html = '
    '; - for (const [resource, actions] of Object.entries(row.resources)) { - html += `
  • ${resource}`; - for (const action of actions) { - html += `${action}`; + +// Function to initialize DataTable +function initialize2DataTable(tableSettings) { + return $(tableSettings.tableId).DataTable({ + processing: true, + serverSide: true, + ajax: { + url: tableSettings.MainUrl, + type: "GET", + }, + columns: [ + { data: null, className: "text-center", render: renderCheckbox }, + { data: "id", className: "text-center" }, + { data: "first_name" }, + { data: "email" }, + { + data: "groups", + render: renderGroups + }, + { data: "principal_type_name", className: "text-center" }, + { data: "is_active", className: "text-center", render: renderSwitch }, + { data: null, className: "text-center", + render: (data, type, row) => { + return renderActions(data, type, row, tableSettings.editUrl); + } } - html += '
  • '; + ], + 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: function (e, dt, node, config) { + archiveAction(e, dt, node, config, tableSettings); + }, + 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": '', "sNext": '' }, + sInfo: "Showing page _PAGE_ of _PAGES_", + sSearch: '', + sSearchPlaceholder: "Search...", + sLengthMenu: " _MENU_", + }, + stripeClasses: [], + lengthMenu: [5, 10, 20, 50], + pageLength: 10, + initComplete: initCompleteCallback + }); +} + +function renderGroups(data, type, row) { + if (type === 'display' && row.groups) { + let html = '
      '; + for (const [index, group] of Object.entries(row.groups)) { + html += `
    • ${group.name}
    • `; } html += '
    '; return html; - } else if (type === 'display' && !row.resources) { - return 'No Permission assigned'; + } else if (type === 'display' && !row.groups) { + return 'No Group assigned'; } else { return ''; } } // Function to reload the DataTable -function reloadDataTable() { - dataTableInstance.ajax.reload(); +function reloadDataTable(tableInstance) { + tableInstance.ajax.reload(); } // Render checkbox @@ -286,7 +411,7 @@ function renderSwitch(data, type, row) { } // Render actions -function renderActions(data, type, row) { +function renderActions(data, type, row, editUrl) { return `
    • @@ -299,15 +424,30 @@ function renderActions(data, type, row) {
    `; } +// Callback function for DataTable initialization complete event +function initCompleteCallback(settings) { + var tableId = '#' + settings.sTableId; // Get the ID of the current table + var api = this.api(); + + // Add event listener for checkbox change + $(document).on('change', tableId + ' input[type="checkbox"]', function () { + var checkedCount = $(tableId + ' tbody input.archive-checkbox:checked').length; + var archiveButton = $(' .buttons-archive'); + console.log("checkbox is checked", checkedCount); + archiveButton.toggle(checkedCount > 0); + }); + +} + // Function to handle archive action -function archiveAction() { +function archiveAction(e, dt, node, config, tableSettings) { // Get all the checked checkboxes - var checkedCheckboxes = $('.archive-checkbox:checked'); + var checkedCheckboxes = dt.$('.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.', + title: 'No Record selected', + text: 'Please select at least one Record to archive.', icon: 'error', showConfirmButton: true }); @@ -330,7 +470,7 @@ function archiveAction() { if (result.isConfirmed) { // Perform archive action $.ajax({ - url: actionUrl, // Replace with your archive endpoint + url: tableSettings.actionUrl, // Replace with your archive endpoint type: 'POST', data: { action: "archive", @@ -346,7 +486,7 @@ function archiveAction() { showConfirmButton: true }); // Optionally, you can reload the DataTable after successful archive - reloadDataTable(); + reloadDataTable(dt); }, error: function(response) { // Show error message @@ -362,33 +502,12 @@ function archiveAction() { }); } -// 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() { +function activeSwitchEventListener(tableInstance) { // Add event listener for switch change event - $('body').on('change', '.switch-input', function() { + tableInstance.on('change', '.switch-input', function() { var rowId = $(this).closest('tr').find('.switch-input').data('id'); var isActive = $(this).prop('checked'); console.log(rowId, isActive) diff --git a/templates/module_iam/iam_principal_group_link_edit.html b/templates/module_iam/iam_principal_group_link_edit.html new file mode 100644 index 0000000..f240d8e --- /dev/null +++ b/templates/module_iam/iam_principal_group_link_edit.html @@ -0,0 +1,64 @@ +{% extends 'base_structure/layout/base_template.html' %} +{% load static %} +{% block stylesheet %} + + + + +{% endblock %} + +{% block content %} + +
    +
    +
    +
    +

    {{operation}} Principal Group Link

    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    + {% csrf_token %} + {% include 'base_structure/includes/dynamic_template_form.html' with form=form %} +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + +{% endblock content %} + +{% block javascript %} + + + + + +{% endblock %} \ No newline at end of file