Merge lp:~mhall119/summit/add-auditing into lp:summit

Proposed by Michael Hall
Status: Needs review
Proposed branch: lp:~mhall119/summit/add-auditing
Merge into: lp:summit
Diff against target: 417 lines (+321/-0)
9 files modified
summit/common/admin/__init__.py (+1/-0)
summit/common/admin/auditadmin.py (+12/-0)
summit/common/audit.py (+131/-0)
summit/common/migrations/0003_add_audit_model.py (+125/-0)
summit/common/models.py (+36/-0)
summit/schedule/models/agendamodel.py (+5/-0)
summit/schedule/models/meetingmodel.py (+5/-0)
summit/schedule/models/summitmodel.py (+5/-0)
summit/settings.py (+1/-0)
To merge this branch: bzr merge lp:~mhall119/summit/add-auditing
Reviewer Review Type Date Requested Status
Summit Hackers Pending
Review via email: mp+150249@code.launchpad.net

Description of the change

Adds change auditing to Summit, Meeting and Agenda

To post a comment you must log in.

Unmerged revisions

484. By Michael Hall

Add auditing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'summit/common/admin/__init__.py'
--- summit/common/admin/__init__.py 2012-03-08 01:55:17 +0000
+++ summit/common/admin/__init__.py 2013-02-24 22:47:21 +0000
@@ -15,3 +15,4 @@
15# along with this program. If not, see <http://www.gnu.org/licenses/>.15# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
17from menuadmin import *17from menuadmin import *
18from auditadmin import *
1819
=== added file 'summit/common/admin/auditadmin.py'
--- summit/common/admin/auditadmin.py 1970-01-01 00:00:00 +0000
+++ summit/common/admin/auditadmin.py 2013-02-24 22:47:21 +0000
@@ -0,0 +1,12 @@
1from django.contrib import admin
2from common.models import AuditRecord, AuditChange
3
4class AuditChangeInline(admin.TabularInline):
5 model = AuditChange
6
7class AuditAdmin(admin.ModelAdmin):
8 inlines = [AuditChangeInline]
9 list_display = ('audit_date', 'change_type', 'app_name', 'model_name', 'model_id', 'user')
10 list_filter = ('change_type', 'app_name', 'model_name')
11
12admin.site.register(AuditRecord, AuditAdmin)
013
=== added file 'summit/common/audit.py'
--- summit/common/audit.py 1970-01-01 00:00:00 +0000
+++ summit/common/audit.py 2013-02-24 22:47:21 +0000
@@ -0,0 +1,131 @@
1"""
2Hook existing objects into auditing sytem through Django signals
3
4Use:
5
6from common.audit import auditSave, auditDelete
7from django.db.models.signals import pre_save, pre_delete
8
9pre_save.connect(auditSave, sender=YourObject)
10pre_delete.connect(auditDelete, sender=YourObject)
11
12"""
13import django
14from models import AuditRecord, AuditChange
15
16
17try:
18 from threading import local
19except ImportError:
20 from django.utils._threading_local import local
21
22_thread_locals = local()
23
24
25def get_current_user():
26 "Return the owner of the current thread"
27 return getattr(_thread_locals, 'user', None)
28
29def get_current_user_id():
30 user = get_current_user()
31 if (user != None):
32 return getattr(user, 'id', 0)
33 else:
34 return 0
35
36def set_current_user(user):
37 "Set the owner of the current thread"
38 _thread_locals.user = user
39
40
41class CaptureRequestUser(object):
42 "Middleware that captures the current HTTP request user for auditing purposes"
43
44 def process_request(self, request):
45 set_current_user(getattr(request, 'user', None))
46
47
48def auditSave(sender, **kwargs):
49 instance = kwargs['instance']
50 if (instance.pk is not None and instance.pk > 0):
51 try:
52 old = instance.__class__.objects.get(id=instance.pk)
53 except:
54 old = instance.__class__()
55 else:
56 old = instance.__class__()
57
58 rec = AuditRecord()
59 rec.user = get_current_user()
60 rec.app_name = instance._meta.app_label;
61 rec.model_name = instance.__class__.__name__
62 rec.model_id = instance.id or 0
63 if instance.id:
64 rec.change_type = rec.AUDIT_MODIFY
65 else:
66 rec.change_type = rec.AUDIT_CREATE
67
68 rec.save()
69
70 fields_changed = rec.change_type == rec.AUDIT_CREATE
71
72 for f in get_audit_fields(instance):
73 try:
74 oldval = getattr(old, f, None)
75 except:
76 oldval = None
77 newval = getattr(instance, f)
78
79 # For some reason oldval will be an int, when the field is infact a
80 # boolean, so this will force oldval to be the right type
81 if isinstance(newval, bool) and isinstance(oldval, int):
82 oldval = bool(oldval)
83
84 if (isinstance(oldval, django.db.models.Model)):
85 oldval = getattr(oldval, 'pk', oldval)
86 if (isinstance(newval, django.db.models.Model)):
87 newval = getattr(newval, 'pk', newval)
88
89 if ((oldval and oldval.__str__().strip() != '') or (newval and newval.__str__().strip() != '')) and not (oldval and newval and oldval.__str__().strip() == newval.__str__().strip()):
90 if hasattr(instance, 'audit_censor') and f in instance.audit_censor:
91 oldval = '*****'
92 newval = '*****'
93 change = AuditChange()
94 change.record = rec
95 change.field_name = f[0:50]
96 change.old_val = str(oldval)[0:255]
97 change.new_val = str(newval)[0:255]
98 change.save()
99 fields_changed = True
100 if not fields_changed:
101 rec.delete()
102
103
104def auditDelete(sender, **kwargs):
105
106 instance = kwargs['instance']
107
108 rec = AuditRecord()
109 rec.user = get_current_user()
110 rec.app_name = instance._meta.app_label;
111 rec.model_name = instance.__class__.__name__
112 rec.model_id = instance.id or 0
113 rec.change_type = rec.AUDIT_DELETE
114 rec.save()
115
116def recordChange(rec, fieldname, oldval, newval):
117 change = AuditChange()
118 change.record = rec
119 change.field_name = fieldname[0:50]
120 change.old_val = str(oldval)[0:255]
121 change.new_val = str(newval)[0:255]
122 change.save()
123
124def get_audit_fields(instance):
125 "Get a list of fields from a model for which value changes should be audited"
126 # The use of _meta is not encouraged, as it is not an external API for Django
127 # But I don't see any other reliable way to get a list of a model's fields.
128 if hasattr(instance, 'audit_ignore'):
129 return [f.column for f in instance._meta.fields if f.name not in instance.audit_ignore]
130 else:
131 return [f.column for f in instance._meta.fields]
0132
=== added file 'summit/common/migrations/0003_add_audit_model.py'
--- summit/common/migrations/0003_add_audit_model.py 1970-01-01 00:00:00 +0000
+++ summit/common/migrations/0003_add_audit_model.py 2013-02-24 22:47:21 +0000
@@ -0,0 +1,125 @@
1# encoding: utf-8
2import datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7class Migration(SchemaMigration):
8
9 def forwards(self, orm):
10
11 # Adding model 'AuditChange'
12 db.create_table('common_auditchange', (
13 ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('record', self.gf('django.db.models.fields.related.ForeignKey')(related_name='changes', to=orm['common.AuditRecord'])),
15 ('field_name', self.gf('django.db.models.fields.CharField')(max_length=50)),
16 ('old_val', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
17 ('new_val', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
18 ))
19 db.send_create_signal('common', ['AuditChange'])
20
21 # Adding model 'AuditRecord'
22 db.create_table('common_auditrecord', (
23 ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
24 ('audit_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
25 ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
26 ('app_name', self.gf('django.db.models.fields.CharField')(max_length=50)),
27 ('model_name', self.gf('django.db.models.fields.CharField')(max_length=50)),
28 ('model_id', self.gf('django.db.models.fields.PositiveIntegerField')()),
29 ('change_type', self.gf('django.db.models.fields.PositiveIntegerField')()),
30 ))
31 db.send_create_signal('common', ['AuditRecord'])
32
33
34 def backwards(self, orm):
35
36 # Deleting model 'AuditChange'
37 db.delete_table('common_auditchange')
38
39 # Deleting model 'AuditRecord'
40 db.delete_table('common_auditrecord')
41
42
43 models = {
44 'auth.group': {
45 'Meta': {'object_name': 'Group'},
46 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
48 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
49 },
50 'auth.permission': {
51 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
52 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
53 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
54 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
56 },
57 'auth.user': {
58 'Meta': {'ordering': "['username']", 'object_name': 'User'},
59 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
60 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
61 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
62 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
63 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
64 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
65 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
66 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
67 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
68 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
69 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
70 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
71 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
72 },
73 'common.auditchange': {
74 'Meta': {'object_name': 'AuditChange'},
75 'field_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
76 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77 'new_val': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
78 'old_val': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
79 'record': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'changes'", 'to': "orm['common.AuditRecord']"})
80 },
81 'common.auditrecord': {
82 'Meta': {'ordering': "['-audit_date']", 'object_name': 'AuditRecord'},
83 'app_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
84 'audit_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
85 'change_type': ('django.db.models.fields.PositiveIntegerField', [], {}),
86 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
87 'model_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
88 'model_name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
89 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
90 },
91 'common.menu': {
92 'Meta': {'object_name': 'Menu'},
93 'base_url': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
94 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
95 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
97 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}),
98 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
99 },
100 'common.menuitem': {
101 'Meta': {'object_name': 'MenuItem'},
102 'anonymous_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
103 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104 'link_url': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
105 'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
106 'menu': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['common.Menu']"}),
107 'order': ('django.db.models.fields.IntegerField', [], {}),
108 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
109 },
110 'contenttypes.contenttype': {
111 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
112 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
113 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
115 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
116 },
117 'sites.site': {
118 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
119 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
120 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
122 }
123 }
124
125 complete_apps = ['common']
0126
=== modified file 'summit/common/models.py'
--- summit/common/models.py 2012-10-02 18:07:24 +0000
+++ summit/common/models.py 2013-02-24 22:47:21 +0000
@@ -2,6 +2,7 @@
22
3from django.contrib.sites.models import Site3from django.contrib.sites.models import Site
4from django.contrib.sites.managers import CurrentSiteManager4from django.contrib.sites.managers import CurrentSiteManager
5from django.contrib.auth.models import User
56
6class Menu(models.Model):7class Menu(models.Model):
7 name = models.CharField(max_length=100)8 name = models.CharField(max_length=100)
@@ -42,3 +43,38 @@
42 43
43 def __unicode__(self):44 def __unicode__(self):
44 return "%s %s. %s" % (self.menu.slug, self.order, self.title)45 return "%s %s. %s" % (self.menu.slug, self.order, self.title)
46
47class AuditRecord(models.Model):
48 audit_date = models.DateTimeField("Date", auto_now_add=True)
49 user = models.ForeignKey(User, null=True)
50 app_name = models.CharField("Application", max_length = 50)
51 model_name = models.CharField("Model", max_length = 50)
52 model_id = models.PositiveIntegerField("Model ID")
53 AUDIT_CREATE = 0
54 AUDIT_MODIFY = 1
55 AUDIT_DELETE = 2
56 AUDIT_CHANGE_TYPES = (
57 (AUDIT_CREATE, 'create'),
58 (AUDIT_MODIFY, 'modify'),
59 (AUDIT_DELETE, 'delete'),
60 )
61 change_type = models.PositiveIntegerField("Change Type", choices = AUDIT_CHANGE_TYPES)
62
63 class Meta:
64 ordering = ['-audit_date']
65 verbose_name = "Audit Record"
66
67 def __unicode__(self):
68 return "%s.%s[%s] @ %s" % (self.app_name, self.model_name, self.model_id, self.audit_date)
69
70class AuditChange(models.Model):
71 record = models.ForeignKey(AuditRecord, related_name='changes')
72 field_name = models.CharField("Field", max_length = 50)
73 old_val = models.CharField("From", max_length = 255, blank=True, null=True)
74 new_val = models.CharField("To", max_length = 255, blank=True, null=True)
75
76 class Meta:
77 verbose_name = "Audit Change"
78
79 def __unicode__(self):
80 return "%s: %s -> %s" % (self.field_name, self.old_val, self.new_val)
4581
=== modified file 'summit/schedule/models/agendamodel.py'
--- summit/schedule/models/agendamodel.py 2012-01-23 01:18:55 +0000
+++ summit/schedule/models/agendamodel.py 2013-02-24 22:47:21 +0000
@@ -20,6 +20,9 @@
20from summit.schedule.models.roommodel import Room20from summit.schedule.models.roommodel import Room
21from summit.schedule.models.meetingmodel import Meeting21from summit.schedule.models.meetingmodel import Meeting
2222
23from common.audit import auditSave, auditDelete
24from django.db.models.signals import pre_save, pre_delete
25
23__all__ = (26__all__ = (
24 'Agenda',27 'Agenda',
25)28)
@@ -48,3 +51,5 @@
4851
49 def __unicode__(self):52 def __unicode__(self):
50 return "%s" % self.meeting53 return "%s" % self.meeting
54pre_save.connect(auditSave, sender=Agenda)
55pre_delete.connect(auditDelete, sender=Agenda)
5156
=== modified file 'summit/schedule/models/meetingmodel.py'
--- summit/schedule/models/meetingmodel.py 2013-02-20 13:57:06 +0000
+++ summit/schedule/models/meetingmodel.py 2013-02-24 22:47:21 +0000
@@ -33,6 +33,9 @@
3333
34from summit.schedule.autoslug import AutoSlugMixin34from summit.schedule.autoslug import AutoSlugMixin
3535
36from common.audit import auditSave, auditDelete
37from django.db.models.signals import pre_save, pre_delete
38
36__all__ = (39__all__ = (
37 'Meeting',40 'Meeting',
38)41)
@@ -601,3 +604,5 @@
601 print "-- could not schedule %s in %s at %s (%s)" % (self, room, slot, e)604 print "-- could not schedule %s in %s at %s (%s)" % (self, room, slot, e)
602605
603 return False606 return False
607pre_save.connect(auditSave, sender=Meeting)
608pre_delete.connect(auditDelete, sender=Meeting)
604609
=== modified file 'summit/schedule/models/summitmodel.py'
--- summit/schedule/models/summitmodel.py 2013-02-21 23:10:17 +0000
+++ summit/schedule/models/summitmodel.py 2013-02-24 22:47:21 +0000
@@ -40,6 +40,9 @@
40from summit.schedule.fields import NameField40from summit.schedule.fields import NameField
41from summit.common import launchpad41from summit.common import launchpad
4242
43from common.audit import auditSave, auditDelete
44from django.db.models.signals import pre_save, pre_delete
45
43__all__ = (46__all__ = (
44 'Summit',47 'Summit',
45 'SummitSprint',48 'SummitSprint',
@@ -512,6 +515,8 @@
512 if attendee is not None:515 if attendee is not None:
513 return self.lead_set.filter(lead=attendee).exists()516 return self.lead_set.filter(lead=attendee).exists()
514 return False517 return False
518pre_save.connect(auditSave, sender=Summit)
519pre_delete.connect(auditDelete, sender=Summit)
515520
516521
517class SummitSprint(models.Model):522class SummitSprint(models.Model):
518523
=== modified file 'summit/settings.py'
--- summit/settings.py 2013-02-04 23:22:10 +0000
+++ summit/settings.py 2013-02-24 22:47:21 +0000
@@ -108,6 +108,7 @@
108 'django.middleware.locale.LocaleMiddleware',108 'django.middleware.locale.LocaleMiddleware',
109 'django.contrib.sessions.middleware.SessionMiddleware',109 'django.contrib.sessions.middleware.SessionMiddleware',
110 'django.contrib.auth.middleware.AuthenticationMiddleware',110 'django.contrib.auth.middleware.AuthenticationMiddleware',
111 'common.audit.CaptureRequestUser',
111)112)
112113
113ROOT_URLCONF = 'summit.urls'114ROOT_URLCONF = 'summit.urls'

Subscribers

People subscribed via source and target branches