added the pagination for auditlog dashboard

This commit is contained in:
Faheedkhan 2025-06-12 16:37:49 +03:00
parent 679188dad8
commit bf17338a29
7 changed files with 270 additions and 242 deletions

View File

@ -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:

View File

@ -1,68 +0,0 @@
{% extends "base.html" %}
{% load i18n custom_filters %}
{% block title %}{% trans "Accounts" %}{% endblock title %}
{% block accounts %}
<a class="nav-link active fw-bold">
{% trans "Accounts"|capfirst %}
<span class="visually-hidden">(current)</span>
</a>
{% endblock %}
{% block content %}
<div class="row mt-4">
<div class="d-flex justify-content-between mb-2">
<h3 class=""><i class="fa-solid fa-book"></i> {% trans "Audit Log Dashboard" %}</h3>
</div>
<!-- Log Type Tabs -->
<div class="mb-4">
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="modellogs-tab" data-bs-toggle="tab" data-bs-target="#modellogs" type="button" role="tab" aria-controls="modellogs" aria-selected="true">
<i class="fas fa-wallet me-2"></i>{% trans "User Actions" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="authlogs-tab" data-bs-toggle="tab" data-bs-target="#authlogs" type="button" role="tab" aria-controls="authlogs" aria-selected="false">
<i class="fas fa-boxes me-2"></i>{% trans "User Login Events" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="requestslog-tab" data-bs-toggle="tab" data-bs-target="#requestslog" type="button" role="tab" aria-controls="requestslog" aria-selected="false">
<i class="fas fa-landmark me-2"></i>{% trans "User Page Requests" %}
</button>
</li>
</ul>
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
<!-- modellogs Tab -->
<div class="tab-pane fade show active" id="modellogs" role="tabpanel" aria-labelledby="modellogs-tab">
{% include "partials/search_box.html" %}
{% include "admin_management/model_logs.html" %}
</div>
<!-- authlogs Tab -->
<div class="tab-pane fade" id="authlogs" role="tabpanel" aria-labelledby="authlogs-tab">
{% include "partials/search_box.html" %}
{% include "admin_management/auth_logs.html" %}
</div>
<!-- requestslog Tab -->
<div class="tab-pane fade" id="requestslog" role="tabpanel" aria-labelledby="requestslog-tab">
{% include "partials/search_box.html" %}
{% include "admin_management/request_logs.html" %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -22,7 +22,7 @@
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
<!-- modellogs Tab -->
{% if auth_events %}
{% if page_obj %}
<div class="table-responsive px-1 scrollbar mt-3">
<table class= "table align-items-center table-flush table-hover">
<thead>
@ -35,7 +35,7 @@
</tr>
</thead>
<tbody class="list">
{% for event in auth_events %}
{% for event in page_obj.object_list %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
@ -49,7 +49,15 @@
</tbody>
</table>
</div>
<div class="d-flex justify-content-end mt-3">
<div class="d-flex">
{% if is_paginated %}
{% include 'partials/pagination.html' %}
{% endif %}
</div>
</div>
{% include 'partials/pagination_audit.html' with q='loginEvents' %}
{% else %}
<p>No authentication audit events found.</p>
{% endif %}

View File

@ -22,69 +22,69 @@
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
<!-- modellogs Tab -->
{% if model_events %}
<div class="table-responsive px-1 scrollbar mt-3">
<table class="table align-items-center table-flush table-hover mt-3">
<thead>
<tr class="bg-body-highlight">
<th>{% trans "Timestamp" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Action" %}</th>
<th>{% trans "Model" %}</th>
<th>{% trans "Object ID" %}</th>
<th>{% trans "Object Representation" %}</th>
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
</tr>
</thead>
<tbody>
{% for event in model_events %}
{% if event.field_changes %}
{% if page_obj %}
<div class="table-responsive px-1 scrollbar mt-3">
<table class="table align-items-center table-flush table-hover mt-3">
<thead>
<tr class="bg-body-highlight">
<th>{% trans "Timestamp" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Action" %}</th>
<th>{% trans "Model" %}</th>
<th>{% trans "Object ID" %}</th>
<th>{% trans "Object Representation" %}</th>
<th>{% trans "Field" %}</th> {# Dedicated column for field name #}
<th>{% trans "Old Value" %}</th> {# Dedicated column for old value #}
<th>{% trans "New Value" %}</th> {# Dedicated column for new value #}
</tr>
</thead>
<tbody>
{% 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 %}
<tr>
{% for change in event.field_changes %}
<tr>
{# Display common event details using rowspan for the first change #}
{% if forloop.first %}
<td rowspan="{{ event.field_changes|length }}">
{{ event.datetime|date:"Y-m-d H:i:s" }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.user.username|default:"Anonymous" }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.event_type_display }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.model_name|title }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.object_id }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.object_repr }}
</td>
{% endif %}
{% if forloop.first %}
<td rowspan="{{ event.field_changes|length }}">
{{ event.datetime|date:"Y-m-d H:i:s" }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.user.username|default:"Anonymous" }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.event_type_display }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.model_name|title }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.object_id }}
</td>
<td rowspan="{{ event.field_changes|length }}">
{{ event.object_repr }}
</td>
{% endif %}
{# Display the specific field change details in their own columns #}
<td><strong>{{ change.field }}</strong></td>
<td>
{% if change.old is not None %}
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
{% else %}
(None)
{% endif %}
</td>
<td>
{% if change.new is not None %}
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
{% else %}
(None)
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<td><strong>{{ change.field }}</strong></td>
<td>
{% if change.old is not None %}
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.old }}</pre>
{% else %}
(None)
{% endif %}
</td>
<td>
{% if change.new is not None %}
<pre style="white-space: pre-wrap; word-break: break-all; font-size: 0.85em; background-color: #f8f9fa; padding: 5px; border-radius: 3px;">{{ change.new }}</pre>
{% else %}
(None)
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
{# Fallback for events with no specific field changes (e.g., CREATE, DELETE) #}
<tr>
<td>{{ event.datetime|date:"Y-m-d H:i:s" }}</td>
@ -94,7 +94,7 @@
<td>{{ event.object_id }}</td>
<td>{{ event.object_repr }}</td>
{# Span the 'Field', 'Old Value', 'New Value' columns #}
<td colspan="3">
<td>
{% if event.event_type_display == "Create" %}
{% trans "Object created." %}
{% elif event.event_type_display == "Delete" %}
@ -110,6 +110,8 @@
</table>
</div>
{% include 'partials/pagination_audit.html' with q='userActions' %}
{% else %}
<p>{% trans "No model change audit events found." %}</p>
{% endif %}

View File

@ -1,18 +1,19 @@
{% load i18n %}
<ul class="nav nav-tabs" id="accountTypeTabs" role="tablist">
<li class="nav-item" role="presentation">
<li class="nav-item me-3" role="presentation">
<a href="{% url 'audit_log_dashboard' %}?q=userActions">
<i class="fas fa-wallet me-2"></i>{% trans "User Actions" %}
<i class="fas fa-history me-2"></i>{% trans "User Actions" %}
</a>
</li>
<li class="nav-item" role="presentation">
<li class="nav-item me-3" role="presentation">
<a href="{% url 'audit_log_dashboard' %}?q=loginEvents">
<i class="fas fa-boxes me-2"></i>{% trans "User LoginEvents" %}
<i class="fas fa-right-to-bracket me-2"></i>{% trans "User Login Events" %}
</a>
</li>
<li class="nav-item" role="presentation">
<a href="{% url 'audit_log_dashboard' %}?q=userRequests">
<i class="fas fa-landmark me-2"></i>{% trans "User PageRequests" %}
<i class="fas fa-file-alt me-2"></i>{% trans "User Page Requests" %}
</a>
</li>
</ul>
</ul>

View File

@ -21,7 +21,7 @@
<div class="tab-content p-3 border border-top-0 rounded-bottom" id="accountTypeTabsContent">
<!-- modellogs Tab -->
{% if request_events %}
{% if page_obj %}
<div class="table-responsive px-1 scrollbar mt-3">
<table class= "table align-items-center table-flush table-hover">
<thead>
@ -34,7 +34,7 @@
</tr>
</thead>
<tbody class="list">
{% for event in request_events %}
{% for event in page_obj.object_list %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap">{{event.datetime}}</td>
@ -48,7 +48,7 @@
</tbody>
</table>
</div>
{% include 'partials/pagination_audit.html' with q='userRequests' %}
{% else %}
<p>No request audit events found.</p>
{% endif %}

View File

@ -0,0 +1,32 @@
{% load i18n static %}
<div class="row align-items-center justify-content-center py-4 pe-0 fs-9">
<div class="col-auto d-flex">
{# Previous Button #}
{% if page_obj.has_previous %}
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Previous' %}">
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}" aria-hidden="true"></span>
<span>{% trans "Previous" %}</span>
</a>
{% else %}
<span class="page-link disabled" aria-disabled="true">
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}right{% else %}left{% endif %}" aria-hidden="true"></span>
<span>{% trans "Previous" %}</span>
</span>
{% endif %}
{# Next Button #}
{% if page_obj.has_next %}
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if q %}&q={{q}}{% endif %}" aria-label="{% trans 'Next' %}">
<span>{% trans "Next" %}</span>
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}" aria-hidden="true"></span>
</a>
{% else %}
<span class="page-link disabled" aria-disabled="true">
<span>{% trans "Next" %}</span>
<span class="fas fa-chevron-{% if LANGUAGE_CODE == 'ar' %}left{% else %}right{% endif %}" aria-hidden="true"></span>
</span>
{% endif %}
</div>
</div>