Skip to content

Commit da4555c

Browse files
authored
Merge pull request #1 from Ankitashinde0/master
Changes done in original Auditlog library for more effective use.
2 parents c3fedc4 + 9a83b8c commit da4555c

File tree

3 files changed

+86
-49
lines changed

3 files changed

+86
-49
lines changed

src/auditlog/admin.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
from django.contrib import admin
22
from .models import LogEntry
33
from .mixins import LogEntryAdminMixin
4+
from .mixins import MiddlewareMixinclass
45
from .filters import ResourceTypeFilter
6+
from .middleware import AuditlogMiddleware
57

68

7-
class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
8-
list_display = ['created', 'resource_url', 'action', 'msg_short', 'user_url']
9-
search_fields = ['timestamp', 'object_repr', 'changes', 'actor__first_name', 'actor__last_name']
10-
list_filter = ['action', ResourceTypeFilter]
9+
class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin,MiddlewareMixinclass):
10+
list_display = ['created','user_url','action','entity_type','object_repr','msg_short','remote_addr']
11+
search_fields = ['changes','remote_addr','object_repr']
12+
list_filter = ['action',ResourceTypeFilter,'timestamp']
1113
readonly_fields = ['created', 'resource_url', 'action', 'user_url', 'msg']
1214
fieldsets = [
1315
(None, {'fields': ['created', 'user_url', 'resource_url']}),
1416
('Changes', {'fields': ['action', 'msg']}),
1517
]
1618

19+
def has_add_permission(self, request): # remove "add" permission
20+
return False
1721

1822
admin.site.register(LogEntry, LogEntryAdmin)

src/auditlog/mixins.py

+62-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import json
2-
2+
from django.utils.html import mark_safe
3+
import datetime
34
from django.conf import settings
5+
from .middleware import AuditlogMiddleware
6+
import threading
7+
import time
8+
9+
410
try:
511
from django.core import urlresolvers
612
except ImportError:
@@ -9,17 +15,36 @@
915
from django.urls.exceptions import NoReverseMatch
1016
except ImportError:
1117
from django.core.urlresolvers import NoReverseMatch
12-
from django.utils.html import format_html
13-
from django.utils.safestring import mark_safe
18+
try:
19+
from django.utils.deprecation import MiddlewareMixin
20+
except ImportError:
21+
MiddlewareMixin = object
1422

1523
MAX = 75
1624

25+
threadlocal = threading.local()
1726

18-
class LogEntryAdminMixin(object):
27+
class MiddlewareMixinclass(MiddlewareMixin):
28+
def disp_remote_addr(self,obj):
29+
return obj.remote_addr
30+
disp_remote_addr.short_description = "IP Address"
1931

32+
class LogEntryAdminMixin(object):
2033
def created(self, obj):
21-
return obj.timestamp.strftime('%Y-%m-%d %H:%M:%S')
22-
created.short_description = 'Created'
34+
return obj.timestamp.strftime('%b. %d, %Y, %I:%M %p') #Displays date in diff format
35+
created.short_description = 'Date'
36+
37+
def entity_type(self,obj):
38+
#entity type for KLC objects
39+
if obj.content_type_id == 0:
40+
return "N/A"
41+
if obj.content_type_id == 8 or obj.content_type_id == 9: #Displays client for all models inside it
42+
return "client"
43+
if obj.content_type_id == 90 or obj.content_type_id == 10: #Displays user for all models inside it
44+
return "user"
45+
46+
return obj.content_type.model
47+
entity_type.short_description = "Entity type"
2348

2449
def user_url(self, obj):
2550
if obj.actor:
@@ -28,10 +53,10 @@ def user_url(self, obj):
2853
try:
2954
link = urlresolvers.reverse(viewname, args=[obj.actor.id])
3055
except NoReverseMatch:
31-
return u'%s' % (obj.actor)
32-
return format_html(u'<a href="{}">{}</a>', link, obj.actor)
33-
34-
return 'system'
56+
return (obj.actor)
57+
return ( obj.actor)
58+
return (obj.object_repr) #Previously returned system ,now changed to return object_repr(username) to display username in last_login entries
59+
user_url.allow_tags = True #Returns user whose last_login is changed which is the username itself.
3560
user_url.short_description = 'User'
3661

3762
def resource_url(self, obj):
@@ -41,32 +66,50 @@ def resource_url(self, obj):
4166
args = [obj.object_pk] if obj.object_id is None else [obj.object_id]
4267
link = urlresolvers.reverse(viewname, args=args)
4368
except NoReverseMatch:
69+
obj_store = obj.object_repr
70+
print('obj_store---- in alice test----',obj_store)
71+
print("type(obj_store-----1st in alice test----",type(obj_store))
72+
print(obj_store.isnumeric())
4473
return obj.object_repr
4574
else:
46-
return format_html(u'<a href="{}">{}</a>', link, obj.object_repr)
47-
resource_url.short_description = 'Resource'
75+
obj_store = str(obj.object_repr)
76+
return (obj.object_repr)
77+
resource_url.allow_tags = True
78+
resource_url.short_description = 'Entity name'
4879

4980
def msg_short(self, obj):
5081
if obj.action == 2:
51-
return '' # delete
82+
return 'Deleted object' # delete
83+
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
84+
return obj.changes #and also when update action-1 is done for client name in Client Group
5285
changes = json.loads(obj.changes)
5386
s = '' if len(changes) == 1 else 's'
5487
fields = ', '.join(changes.keys())
5588
if len(fields) > MAX:
5689
i = fields.rfind(' ', 0, MAX)
5790
fields = fields[:i] + ' ..'
5891
return '%d change%s: %s' % (len(changes), s, fields)
59-
msg_short.short_description = 'Changes'
92+
msg_short.short_description = 'Description'
6093

6194
def msg(self, obj):
6295
if obj.action == 2:
6396
return '' # delete
6497
changes = json.loads(obj.changes)
65-
msg = '<table><tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>'
98+
msg = '<table><tr><th>No.</th><th>Field</th><th>From</th><th>To</th></tr>'
6699
for i, field in enumerate(sorted(changes), 1):
67-
value = [i, field] + (['***', '***'] if field == 'password' else changes[field])
68-
msg += format_html('<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>', *value)
69-
100+
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 ****
101+
#changes for last_login field in logging entries
102+
if field == 'last_login':
103+
for i in range(len(changes[field])): #iterating list
104+
ologin_date = changes[field][0] #storing value at index 0 as old login date
105+
ologin_date = datetime.datetime.strptime(ologin_date, '%Y-%m-%d %H:%M:%S.%f') #converting string into datetime obj
106+
ologin_date = ologin_date.strftime("%m/%d/%Y %I:%M %p") #converting date into string type
107+
nlogin_date = changes[field][1] #storing value at index 1 as new login date
108+
nlogin_date = datetime.datetime.strptime(nlogin_date, '%Y-%m-%d %H:%M:%S.%f') #converting string into datetime obj
109+
nlogin_date = nlogin_date.strftime("%m/%d/%Y %I:%M %p") #converting date into string type
110+
value = [i,field] + [ologin_date,nlogin_date]
111+
msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>' % tuple(value)
70112
msg += '</table>'
71-
return mark_safe(msg)
113+
return mark_safe(msg) #mark_safe is used to return html code in Python
114+
msg.allow_tags = True
72115
msg.short_description = 'Changes'

src/auditlog/models.py

+16-26
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.contrib.contenttypes.fields import GenericRelation
88
from django.contrib.contenttypes.models import ContentType
99
from django.core.exceptions import FieldDoesNotExist
10-
from django.db import models, DEFAULT_DB_ALIAS
10+
from django.db import models
1111
from django.db.models import QuerySet, Q
1212
from django.utils import formats, timezone
1313
from django.utils.encoding import python_2_unicode_compatible, smart_text
@@ -164,18 +164,22 @@ class Action:
164164
CREATE = 0
165165
UPDATE = 1
166166
DELETE = 2
167+
DOWNLOAD = 3
168+
KLC = 4
167169

168170
choices = (
169-
(CREATE, _("create")),
170-
(UPDATE, _("update")),
171-
(DELETE, _("delete")),
171+
(CREATE, _("Create")),
172+
(UPDATE, _("Update")),
173+
(DELETE, _("Delete")),
174+
(DOWNLOAD,_("Download")),
175+
(KLC,_("KLC"))
172176
)
173177

174-
content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE, related_name='+', verbose_name=_("content type"))
178+
content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE,related_name='+', verbose_name=_("content type"))
175179
object_pk = models.CharField(db_index=True, max_length=255, verbose_name=_("object pk"))
176180
object_id = models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name=_("object id"))
177-
object_repr = models.TextField(verbose_name=_("object representation"))
178-
action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("action"))
181+
object_repr = models.TextField(verbose_name=_("Entity name"))
182+
action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("Operation"))
179183
changes = models.TextField(blank=True, verbose_name=_("change message"))
180184
actor = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, related_name='+', verbose_name=_("actor"))
181185
remote_addr = models.GenericIPAddressField(blank=True, null=True, verbose_name=_("remote address"))
@@ -197,6 +201,10 @@ def __str__(self):
197201
fstring = _("Updated {repr:s}")
198202
elif self.action == self.Action.DELETE:
199203
fstring = _("Deleted {repr:s}")
204+
elif self.action == self.Action.DOWNLOAD:
205+
fstring = _("Downloaded {repr:s}")
206+
elif self.action == self.Action.KLC:
207+
fstring = _("Kaseya Live Connect {repr:s}")
200208
else:
201209
fstring = _("Logged {repr:s}")
202210

@@ -321,13 +329,9 @@ class AuditlogHistoryField(GenericRelation):
321329
322330
:param pk_indexable: Whether the primary key for this model is not an :py:class:`int` or :py:class:`long`.
323331
:type pk_indexable: bool
324-
:param delete_related: By default, including a generic relation into a model will cause all related objects to be
325-
cascade-deleted when the parent object is deleted. Passing False to this overrides this behavior, retaining
326-
the full auditlog history for the object. Defaults to True, because that's Django's default behavior.
327-
:type delete_related: bool
328332
"""
329333

330-
def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
334+
def __init__(self, pk_indexable=True, **kwargs):
331335
kwargs['to'] = LogEntry
332336

333337
if pk_indexable:
@@ -336,22 +340,8 @@ def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
336340
kwargs['object_id_field'] = 'object_pk'
337341

338342
kwargs['content_type_field'] = 'content_type'
339-
self.delete_related = delete_related
340343
super(AuditlogHistoryField, self).__init__(**kwargs)
341344

342-
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
343-
"""
344-
Return all objects related to ``objs`` via this ``GenericRelation``.
345-
"""
346-
if self.delete_related:
347-
return super(AuditlogHistoryField, self).bulk_related_objects(objs, using)
348-
349-
# When deleting, Collector.collect() finds related objects using this
350-
# method. However, because we don't want to delete these related
351-
# objects, we simply return an empty list.
352-
return []
353-
354-
355345
# South compatibility for AuditlogHistoryField
356346
try:
357347
from south.modelsinspector import add_introspection_rules

0 commit comments

Comments
 (0)