diff --git a/src/auditlog/admin.py b/src/auditlog/admin.py
index a4c60fc1..5b07d923 100644
--- a/src/auditlog/admin.py
+++ b/src/auditlog/admin.py
@@ -1,18 +1,22 @@
from django.contrib import admin
from .models import LogEntry
from .mixins import LogEntryAdminMixin
+from .mixins import MiddlewareMixinclass
from .filters import ResourceTypeFilter
+from .middleware import AuditlogMiddleware
-class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
- list_display = ['created', 'resource_url', 'action', 'msg_short', 'user_url']
- search_fields = ['timestamp', 'object_repr', 'changes', 'actor__first_name', 'actor__last_name']
- list_filter = ['action', ResourceTypeFilter]
+class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin,MiddlewareMixinclass):
+ list_display = ['created','user_url','action','entity_type','object_repr','msg_short','remote_addr']
+ search_fields = ['changes','remote_addr','object_repr']
+ list_filter = ['action',ResourceTypeFilter,'timestamp']
readonly_fields = ['created', 'resource_url', 'action', 'user_url', 'msg']
fieldsets = [
(None, {'fields': ['created', 'user_url', 'resource_url']}),
('Changes', {'fields': ['action', 'msg']}),
]
+ def has_add_permission(self, request): # remove "add" permission
+ return False
admin.site.register(LogEntry, LogEntryAdmin)
diff --git a/src/auditlog/mixins.py b/src/auditlog/mixins.py
index 5a0b829b..2918d405 100644
--- a/src/auditlog/mixins.py
+++ b/src/auditlog/mixins.py
@@ -1,6 +1,12 @@
import json
-
+from django.utils.html import mark_safe
+import datetime
from django.conf import settings
+from .middleware import AuditlogMiddleware
+import threading
+import time
+
+
try:
from django.core import urlresolvers
except ImportError:
@@ -9,17 +15,36 @@
from django.urls.exceptions import NoReverseMatch
except ImportError:
from django.core.urlresolvers import NoReverseMatch
-from django.utils.html import format_html
-from django.utils.safestring import mark_safe
+try:
+ from django.utils.deprecation import MiddlewareMixin
+except ImportError:
+ MiddlewareMixin = object
MAX = 75
+threadlocal = threading.local()
-class LogEntryAdminMixin(object):
+class MiddlewareMixinclass(MiddlewareMixin):
+ def disp_remote_addr(self,obj):
+ return obj.remote_addr
+ disp_remote_addr.short_description = "IP Address"
+class LogEntryAdminMixin(object):
def created(self, obj):
- return obj.timestamp.strftime('%Y-%m-%d %H:%M:%S')
- created.short_description = 'Created'
+ return obj.timestamp.strftime('%b. %d, %Y, %I:%M %p') #Displays date in diff format
+ created.short_description = 'Date'
+
+ def entity_type(self,obj):
+ #entity type for KLC objects
+ if obj.content_type_id == 0:
+ return "N/A"
+ if obj.content_type_id == 8 or obj.content_type_id == 9: #Displays client for all models inside it
+ return "client"
+ if obj.content_type_id == 90 or obj.content_type_id == 10: #Displays user for all models inside it
+ return "user"
+
+ return obj.content_type.model
+ entity_type.short_description = "Entity type"
def user_url(self, obj):
if obj.actor:
@@ -28,10 +53,10 @@ def user_url(self, obj):
try:
link = urlresolvers.reverse(viewname, args=[obj.actor.id])
except NoReverseMatch:
- return u'%s' % (obj.actor)
- return format_html(u'{}', link, obj.actor)
-
- return 'system'
+ return (obj.actor)
+ return ( obj.actor)
+ return (obj.object_repr) #Previously returned system ,now changed to return object_repr(username) to display username in last_login entries
+ user_url.allow_tags = True #Returns user whose last_login is changed which is the username itself.
user_url.short_description = 'User'
def resource_url(self, obj):
@@ -41,14 +66,22 @@ def resource_url(self, obj):
args = [obj.object_pk] if obj.object_id is None else [obj.object_id]
link = urlresolvers.reverse(viewname, args=args)
except NoReverseMatch:
+ obj_store = obj.object_repr
+ print('obj_store---- in alice test----',obj_store)
+ print("type(obj_store-----1st in alice test----",type(obj_store))
+ print(obj_store.isnumeric())
return obj.object_repr
else:
- return format_html(u'{}', link, obj.object_repr)
- resource_url.short_description = 'Resource'
+ obj_store = str(obj.object_repr)
+ return (obj.object_repr)
+ resource_url.allow_tags = True
+ resource_url.short_description = 'Entity name'
def msg_short(self, obj):
if obj.action == 2:
- return '' # delete
+ return 'Deleted object' # delete
+ if obj.action == 3 or obj.action == 4 or obj.action == 1 and obj.content_type_id == 108 and obj.additional_data == "Client_name": #to display changes for actions of download-3,KLC-4
+ return obj.changes #and also when update action-1 is done for client name in Client Group
changes = json.loads(obj.changes)
s = '' if len(changes) == 1 else 's'
fields = ', '.join(changes.keys())
@@ -56,17 +89,27 @@ def msg_short(self, obj):
i = fields.rfind(' ', 0, MAX)
fields = fields[:i] + ' ..'
return '%d change%s: %s' % (len(changes), s, fields)
- msg_short.short_description = 'Changes'
+ msg_short.short_description = 'Description'
def msg(self, obj):
if obj.action == 2:
return '' # delete
changes = json.loads(obj.changes)
- msg = '
# | Field | From | To |
'
+ msg = 'No. | Field | From | To |
'
for i, field in enumerate(sorted(changes), 1):
- value = [i, field] + (['***', '***'] if field == 'password' else changes[field])
- msg += format_html('{} | {} | {} | {} |
', *value)
-
+ value = [i, field] + (['***', '***'] if field == 'password' or field == 'kaseya_password' or field == 'webroot_password' or field == 'db_password' or field == 'nlm_server_password' else changes[field]) #to display values of password fields as ****
+ #changes for last_login field in logging entries
+ if field == 'last_login':
+ for i in range(len(changes[field])): #iterating list
+ ologin_date = changes[field][0] #storing value at index 0 as old login date
+ ologin_date = datetime.datetime.strptime(ologin_date, '%Y-%m-%d %H:%M:%S.%f') #converting string into datetime obj
+ ologin_date = ologin_date.strftime("%m/%d/%Y %I:%M %p") #converting date into string type
+ nlogin_date = changes[field][1] #storing value at index 1 as new login date
+ nlogin_date = datetime.datetime.strptime(nlogin_date, '%Y-%m-%d %H:%M:%S.%f') #converting string into datetime obj
+ nlogin_date = nlogin_date.strftime("%m/%d/%Y %I:%M %p") #converting date into string type
+ value = [i,field] + [ologin_date,nlogin_date]
+ msg += '%s | %s | %s | %s |
' % tuple(value)
msg += '
'
- return mark_safe(msg)
+ return mark_safe(msg) #mark_safe is used to return html code in Python
+ msg.allow_tags = True
msg.short_description = 'Changes'
diff --git a/src/auditlog/models.py b/src/auditlog/models.py
index 0832f2c1..82832bfd 100644
--- a/src/auditlog/models.py
+++ b/src/auditlog/models.py
@@ -7,7 +7,7 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist
-from django.db import models, DEFAULT_DB_ALIAS
+from django.db import models
from django.db.models import QuerySet, Q
from django.utils import formats, timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
@@ -164,18 +164,22 @@ class Action:
CREATE = 0
UPDATE = 1
DELETE = 2
+ DOWNLOAD = 3
+ KLC = 4
choices = (
- (CREATE, _("create")),
- (UPDATE, _("update")),
- (DELETE, _("delete")),
+ (CREATE, _("Create")),
+ (UPDATE, _("Update")),
+ (DELETE, _("Delete")),
+ (DOWNLOAD,_("Download")),
+ (KLC,_("KLC"))
)
- content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE, related_name='+', verbose_name=_("content type"))
+ content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE,related_name='+', verbose_name=_("content type"))
object_pk = models.CharField(db_index=True, max_length=255, verbose_name=_("object pk"))
object_id = models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name=_("object id"))
- object_repr = models.TextField(verbose_name=_("object representation"))
- action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("action"))
+ object_repr = models.TextField(verbose_name=_("Entity name"))
+ action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("Operation"))
changes = models.TextField(blank=True, verbose_name=_("change message"))
actor = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, related_name='+', verbose_name=_("actor"))
remote_addr = models.GenericIPAddressField(blank=True, null=True, verbose_name=_("remote address"))
@@ -197,6 +201,10 @@ def __str__(self):
fstring = _("Updated {repr:s}")
elif self.action == self.Action.DELETE:
fstring = _("Deleted {repr:s}")
+ elif self.action == self.Action.DOWNLOAD:
+ fstring = _("Downloaded {repr:s}")
+ elif self.action == self.Action.KLC:
+ fstring = _("Kaseya Live Connect {repr:s}")
else:
fstring = _("Logged {repr:s}")
@@ -321,13 +329,9 @@ class AuditlogHistoryField(GenericRelation):
:param pk_indexable: Whether the primary key for this model is not an :py:class:`int` or :py:class:`long`.
:type pk_indexable: bool
- :param delete_related: By default, including a generic relation into a model will cause all related objects to be
- cascade-deleted when the parent object is deleted. Passing False to this overrides this behavior, retaining
- the full auditlog history for the object. Defaults to True, because that's Django's default behavior.
- :type delete_related: bool
"""
- def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
+ def __init__(self, pk_indexable=True, **kwargs):
kwargs['to'] = LogEntry
if pk_indexable:
@@ -336,22 +340,8 @@ def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
kwargs['object_id_field'] = 'object_pk'
kwargs['content_type_field'] = 'content_type'
- self.delete_related = delete_related
super(AuditlogHistoryField, self).__init__(**kwargs)
- def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
- """
- Return all objects related to ``objs`` via this ``GenericRelation``.
- """
- if self.delete_related:
- return super(AuditlogHistoryField, self).bulk_related_objects(objs, using)
-
- # When deleting, Collector.collect() finds related objects using this
- # method. However, because we don't want to delete these related
- # objects, we simply return an empty list.
- return []
-
-
# South compatibility for AuditlogHistoryField
try:
from south.modelsinspector import add_introspection_rules