From bf17338a29e54e3f598dea659221e7b3dd5907ad Mon Sep 17 00:00:00 2001 From: Faheedkhan Date: Thu, 12 Jun 2025 16:37:49 +0300 Subject: [PATCH] added the pagination for auditlog dashboard --- inventory/views.py | 257 +++++++++++------- .../admin_management/audit_log_dashboard.html | 68 ----- templates/admin_management/auth_logs.html | 14 +- templates/admin_management/model_logs.html | 122 +++++---- templates/admin_management/nav.html | 13 +- templates/admin_management/request_logs.html | 6 +- templates/partials/pagination_audit.html | 32 +++ 7 files changed, 270 insertions(+), 242 deletions(-) delete mode 100644 templates/admin_management/audit_log_dashboard.html create mode 100644 templates/partials/pagination_audit.html diff --git a/inventory/views.py b/inventory/views.py index ed462472..7edd9fdd 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -31,7 +31,7 @@ from django.forms import HiddenInput, ValidationError from django.shortcuts import HttpResponse from django.db.models import Sum, F, Count -from django.core.paginator import Paginator +from django.core.paginator import Paginator,EmptyPage, PageNotAnInteger from django.contrib.auth.models import User from django.contrib.auth.models import Group from django.db.models import Value @@ -8273,106 +8273,26 @@ def user_management(request): } return render(request, "admin_management/user_management.html", context) -#audit log Management - -# class AuditLogDashboardView(TemplateView): -# """ -# Displays a dashboard with tabs for different audit log types. -# Fetches all necessary log data to pass to the template. -# """ -# template_name = 'admin_management/audit_log_dashboard.html' # Name of your main template with tabs - -# def get_context_data(self, **kwargs): -# context = super().get_context_data(**kwargs) - -# # Fetch data for each log type -# # Order by 'datetime' field as per easy_audit models -# context['model_events'] = CRUDEvent.objects.all().order_by('-datetime') -# context['auth_events'] = LoginEvent.objects.all().order_by('-datetime') -# context['request_events'] = RequestEvent.objects.all().order_by('-datetime') -# return context - -def AuditLogDashboardView(request): - query=request.GET.get('q') - context={} - model_events_raw = CRUDEvent.objects.all().order_by('-datetime') - processed_model_events = [] - - for event in model_events_raw: - # Create a base dictionary for each event's data - event_data = { - 'datetime': event.datetime, - 'user': event.user, - 'event_type_display': event.get_event_type_display(), - 'model_name': event.content_type.model, - 'object_id': event.object_id, - 'object_repr': event.object_repr, - 'field_changes': [] # This will be a list of dicts: [{'field': 'name', 'old': 'A', 'new': 'B'}] - } - - if event.changed_fields: - try: - print(f"Debugging CRUDEvent ID: {event.id}, User: {event.user}, Model: {event.content_type.model}") - print(f"Raw event.changed_fields: '{event.changed_fields}' (Type: {type(event.changed_fields)})") - changes = json.loads(event.changed_fields) - #change_fields returns 'null' for no changes to a field i.e. if the field remains the same - if isinstance(changes, dict): # Check if 'changes' is a dictionary - for field_name, values in changes.items(): - old_value = values[0] if isinstance(values, list) and len(values) > 0 else None - new_value = values[1] if isinstance(values, list) and len(values) > 1 else None - event_data['field_changes'].append({ - 'field': field_name, - 'old': old_value, - 'new': new_value - }) - elif changes is None: # Handle case where JSON was 'null' string like when no changes detected for a field or if it saved without a change - - event_data['field_changes'].append({ - 'field': 'Info', - 'old': '', - 'new': 'No specific field changes recorded (JSON was null)' - }) - else: # Handle valid JSON but not a dictionary (e.g., "[]", 123) - event_data['field_changes'].append({ - 'field': 'Error', - 'old': '', - 'new': f'Unexpected JSON format: {type(changes).__name__}' - }) - except json.JSONDecodeError: - # Handle invalid JSON; you might log this error - event_data['field_changes'].append({ - 'field': 'Error', - 'old': '', - 'new': 'Invalid JSON in changed_fields' - }) - - processed_model_events.append(event_data) - - context['model_events'] = processed_model_events - context['auth_events'] = LoginEvent.objects.all().order_by('-datetime') - context['request_events'] = RequestEvent.objects.all().order_by('-datetime') - if(query=='userActions'): - return render(request,'admin_management/model_logs.html',context) - elif(query=='loginEvents'): - return render(request,'admin_management/auth_logs.html',context) - else: - return render(request,'admin_management/request_logs.html',context) - -# class AuditLogDashboardView(TemplateView): -# template_name = 'admin_management/audit_log_dashboard.html' -# paginate_by = 20 -# def get_context_data(self, **kwargs): -# context = super().get_context_data(**kwargs) -# # Process CRUD (Model Change) Events +# def AuditLogDashboardView(request): +# q=request.GET.get('q') + +# context={} # model_events_raw = CRUDEvent.objects.all().order_by('-datetime') # processed_model_events = [] +# page = request.GET.get('page', 1) -# for event in model_events_raw: + + +# # context['model_events'] = processed_model_events +# # context['auth_events'] = LoginEvent.objects.all().order_by('-datetime') +# # context['request_events'] = RequestEvent.objects.all().order_by('-datetime') +# if(q=='userActions'): +# for event in model_events_raw: # # Create a base dictionary for each event's data # event_data = { # 'datetime': event.datetime, @@ -8386,8 +8306,8 @@ def AuditLogDashboardView(request): # if event.changed_fields: # try: -# print(f"Debugging CRUDEvent ID: {event.id}, User: {event.user}, Model: {event.content_type.model}") -# print(f"Raw event.changed_fields: '{event.changed_fields}' (Type: {type(event.changed_fields)})") +# # print(f"Debugging CRUDEvent ID: {event.id}, User: {event.user}, Model: {event.content_type.model}") +# # print(f"Raw event.changed_fields: '{event.changed_fields}' (Type: {type(event.changed_fields)})") # changes = json.loads(event.changed_fields) # #change_fields returns 'null' for no changes to a field i.e. if the field remains the same # if isinstance(changes, dict): # Check if 'changes' is a dictionary @@ -8418,19 +8338,152 @@ def AuditLogDashboardView(request): # 'field': 'Error', # 'old': '', # 'new': 'Invalid JSON in changed_fields' -# }) +# }) # processed_model_events.append(event_data) - -# context['model_events'] = processed_model_events -# context['auth_events'] = LoginEvent.objects.all().order_by('-datetime') -# context['request_events'] = RequestEvent.objects.all().order_by('-datetime') - -# return context +# paginator=Paginator(processed_model_events,10) +# page_obj=paginator.page(page) +# context={ +# 'page_obj':page_obj +# } +# return render(request,'admin_management/model_logs.html',context) + +# elif(q=='loginEvents'): +# auth_events = LoginEvent.objects.all().order_by('-datetime') +# paginator=Paginator(auth_events,10) +# page_obj=paginator.page(page) +# context={ +# 'page_obj':page_obj +# } +# return render(request,'admin_management/auth_logs.html',context) +# else: +# request_events= RequestEvent.objects.all().order_by('-datetime') +# paginator=Paginator(request_events,10) +# page_obj=paginator.page(page) +# context={ +# 'page_obj':page_obj +# } +# return render(request,'admin_management/request_logs.html',context) + +def AuditLogDashboardView(request): + """ + Displays audit logs (User Actions, Login Events, Request Events) with pagination. + Log type is determined by the 'q' query parameter (e.g., ?q=userActions). + Pagination page number is passed as a query parameter (e.g., ?page=2). + """ + q = request.GET.get('q') # Get the log type from the 'q' query parameter + current_pagination_page = request.GET.get('page', 1) + context = {} + template_name = None + logs_per_page = 30 # Define logs per page once + + # --- Determine Data Source and Template based on 'q' parameter --- + if q=='userRequests': # This block handles cases where 'q' is 'requestEvents', None, or any other invalid value. + # It defaults to Request Logs if 'q' is not 'userActions' or 'loginEvents'. + template_name = 'admin_management/request_logs.html' + context['title'] = 'Request Logs Dashboard' + request_events = RequestEvent.objects.all().order_by('-datetime') + paginator = Paginator(request_events, logs_per_page) + try: + page_obj = paginator.page(current_pagination_page) + except PageNotAnInteger: + page_obj = paginator.page(1) + except EmptyPage: + page_obj = paginator.page(paginator.num_pages) + + + elif q == 'loginEvents': + template_name = 'admin_management/auth_logs.html' + context['title'] = 'Login Events Dashboard' + auth_events = LoginEvent.objects.all().order_by('-datetime') + paginator = Paginator(auth_events, logs_per_page) + try: + page_obj = paginator.page(current_pagination_page) + except PageNotAnInteger: + page_obj = paginator.page(1) + except EmptyPage: + page_obj = paginator.page(paginator.num_pages) + + else: + template_name = 'admin_management/model_logs.html' + context['title'] = 'User Actions Dashboard' + + # OPTIMIZATION: Get the QuerySet but don't evaluate it yet + model_events_queryset = CRUDEvent.objects.all().order_by('-datetime') + + # 1. Paginate the raw QuerySet FIRST + paginator = Paginator(model_events_queryset, logs_per_page) + + try: + # Get the page object, which contains only the raw QuerySet objects for the current page + page_obj_raw = paginator.page(current_pagination_page) + except PageNotAnInteger: + page_obj_raw = paginator.page(1) + except EmptyPage: + page_obj_raw = paginator.page(paginator.num_pages) + + # 2. Now, process 'field_changes' ONLY for the events on the current page + processed_model_events_for_page = [] + for event in page_obj_raw.object_list: # Loop only through the current page's items + event_data = { + 'datetime': event.datetime, + 'user': event.user, + 'event_type_display': event.get_event_type_display(), + 'model_name': event.content_type.model, + 'object_id': event.object_id, + 'object_repr': event.object_repr, + 'field_changes': [] + } + + if event.changed_fields: + try: + changes = json.loads(event.changed_fields) + if isinstance(changes, dict): + for field_name, values in changes.items(): + old_value = values[0] if isinstance(values, list) and len(values) > 0 else None + new_value = values[1] if isinstance(values, list) and len(values) > 1 else None + event_data['field_changes'].append({ + 'field': field_name, + 'old': old_value, + 'new': new_value + }) + elif changes is None: + event_data['field_changes'].append({ + 'field': 'Info', + 'old': '', + 'new': 'No specific field changes recorded (JSON was null)' + }) + else: # Handle valid JSON but not a dictionary (e.g., "[]", 123) + event_data['field_changes'].append({ + 'field': 'Error', + 'old': '', + 'new': f'Unexpected JSON format: {type(changes).__name__}' + }) + except json.JSONDecodeError: + # Handle invalid JSON; you might log this error + event_data['field_changes'].append({ + 'field': 'Error', + 'old': '', + 'new': 'Invalid JSON in changed_fields' + }) + processed_model_events_for_page.append(event_data) + + # 3. Replace the object_list of the original page_obj with the processed data + # This keeps all pagination properties (has_next, number, etc.) intact. + page_obj_raw.object_list = processed_model_events_for_page + page_obj = page_obj_raw # This will be passed to the context + + # Pass the final page object to the context + context['page_obj'] = page_obj + + return render(request, template_name, context) + + + def activate_account(request, content_type, slug): try: diff --git a/templates/admin_management/audit_log_dashboard.html b/templates/admin_management/audit_log_dashboard.html deleted file mode 100644 index 21213e3c..00000000 --- a/templates/admin_management/audit_log_dashboard.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "base.html" %} -{% load i18n custom_filters %} -{% block title %}{% trans "Accounts" %}{% endblock title %} -{% block accounts %} - - {% trans "Accounts"|capfirst %} - (current) - -{% endblock %} -{% block content %} -
- -
-

{% trans "Audit Log Dashboard" %}

-
- - -
- - -
- -
- {% include "partials/search_box.html" %} - {% include "admin_management/model_logs.html" %} - -
- - -
- {% include "partials/search_box.html" %} - {% include "admin_management/auth_logs.html" %} - -
- - -
- {% include "partials/search_box.html" %} - {% include "admin_management/request_logs.html" %} -
- - -
-
-
- - - - -{% endblock %} - diff --git a/templates/admin_management/auth_logs.html b/templates/admin_management/auth_logs.html index 5101abd2..26a2643c 100644 --- a/templates/admin_management/auth_logs.html +++ b/templates/admin_management/auth_logs.html @@ -22,7 +22,7 @@
- {% if auth_events %} + {% if page_obj %}
@@ -35,7 +35,7 @@ - {% for event in auth_events %} + {% for event in page_obj.object_list %} @@ -49,7 +49,15 @@
{{event.datetime}}
- +
+
+ {% if is_paginated %} + {% include 'partials/pagination.html' %} + {% endif %} +
+
+ {% include 'partials/pagination_audit.html' with q='loginEvents' %} + {% else %}

No authentication audit events found.

{% endif %} diff --git a/templates/admin_management/model_logs.html b/templates/admin_management/model_logs.html index ab648cb7..3362cb52 100644 --- a/templates/admin_management/model_logs.html +++ b/templates/admin_management/model_logs.html @@ -22,69 +22,69 @@
- {% if model_events %} -
- - - - - - - - - - {# Dedicated column for field name #} - {# Dedicated column for old value #} - {# Dedicated column for new value #} - - - - {% for event in model_events %} - {% if event.field_changes %} + {% if page_obj %} +
+
{% trans "Timestamp" %}{% trans "User" %}{% trans "Action" %}{% trans "Model" %}{% trans "Object ID" %}{% trans "Object Representation" %}{% trans "Field" %}{% trans "Old Value" %}{% trans "New Value" %}
+ + + + + + + + + {# Dedicated column for field name #} + {# Dedicated column for old value #} + {# Dedicated column for new value #} + + + + {% for event in page_obj.object_list %} + {% if event.field_changes %} {# Loop through each individual field change for this event #} - {% for change in event.field_changes %} - + {% for change in event.field_changes %} + {# Display common event details using rowspan for the first change #} - {% if forloop.first %} - - - - - - - {% endif %} + {% if forloop.first %} + + + + + + + {% endif %} {# Display the specific field change details in their own columns #} - - - - - {% endfor %} - {% else %} + + + + + {% endfor %} + {% else %} {# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #} @@ -94,7 +94,7 @@ {# Span the 'Field', 'Old Value', 'New Value' columns #} -
{% trans "Timestamp" %}{% trans "User" %}{% trans "Action" %}{% trans "Model" %}{% trans "Object ID" %}{% trans "Object Representation" %}{% trans "Field" %}{% trans "Old Value" %}{% trans "New Value" %}
- {{ event.datetime|date:"Y-m-d H:i:s" }} - - {{ event.user.username|default:"Anonymous" }} - - {{ event.event_type_display }} - - {{ event.model_name|title }} - - {{ event.object_id }} - - {{ event.object_repr }} - + {{ event.datetime|date:"Y-m-d H:i:s" }} + + {{ event.user.username|default:"Anonymous" }} + + {{ event.event_type_display }} + + {{ event.model_name|title }} + + {{ event.object_id }} + + {{ event.object_repr }} + {{ change.field }} - {% if change.old is not None %} -
{{ change.old }}
- {% else %} - (None) - {% endif %} -
- {% if change.new is not None %} -
{{ change.new }}
- {% else %} - (None) - {% endif %} -
{{ change.field }} + {% if change.old is not None %} +
{{ change.old }}
+ {% else %} + (None) + {% endif %} +
+ {% if change.new is not None %} +
{{ change.new }}
+ {% else %} + (None) + {% endif %} +
{{ event.datetime|date:"Y-m-d H:i:s" }}{{ event.object_id }} {{ event.object_repr }} + {% if event.event_type_display == "Create" %} {% trans "Object created." %} {% elif event.event_type_display == "Delete" %} @@ -110,6 +110,8 @@
+{% include 'partials/pagination_audit.html' with q='userActions' %} + {% else %}

{% trans "No model change audit events found." %}

{% endif %} diff --git a/templates/admin_management/nav.html b/templates/admin_management/nav.html index 735bef59..ac379dea 100644 --- a/templates/admin_management/nav.html +++ b/templates/admin_management/nav.html @@ -1,18 +1,19 @@ {% load i18n %} + diff --git a/templates/admin_management/request_logs.html b/templates/admin_management/request_logs.html index d14f86e1..1168c676 100644 --- a/templates/admin_management/request_logs.html +++ b/templates/admin_management/request_logs.html @@ -21,7 +21,7 @@
- {% if request_events %} + {% if page_obj %}
@@ -34,7 +34,7 @@ - {% for event in request_events %} + {% for event in page_obj.object_list %} @@ -48,7 +48,7 @@
{{event.datetime}}
- + {% include 'partials/pagination_audit.html' with q='userRequests' %} {% else %}

No request audit events found.

{% endif %} diff --git a/templates/partials/pagination_audit.html b/templates/partials/pagination_audit.html new file mode 100644 index 00000000..ff544e2f --- /dev/null +++ b/templates/partials/pagination_audit.html @@ -0,0 +1,32 @@ +{% load i18n static %} + +
+
+ {# Previous Button #} + {% if page_obj.has_previous %} + + + {% trans "Previous" %} + + {% else %} + + + {% trans "Previous" %} + + {% endif %} + + + {# Next Button #} + {% if page_obj.has_next %} + + {% trans "Next" %} + + + {% else %} + + {% trans "Next" %} + + + {% endif %} +
+
\ No newline at end of file