django-adminaudit-0.3.2/ 0000775 0001750 0001750 00000000000 11746507402 015747 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/PKG-INFO 0000664 0001750 0001750 00000001207 11746507402 017044 0 ustar lukasz lukasz 0000000 0000000 Metadata-Version: 1.1
Name: django-adminaudit
Version: 0.3.2
Summary: Extends Django's admin logging capabilities
Home-page: https://launchpad.net/django-adminaudit
Author: Łukasz Czyżykowski
Author-email: lukasz.czyzykowski@canonical.com
License: LGPLv3
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Framework :: Django
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
django-adminaudit-0.3.2/adminaudit/ 0000775 0001750 0001750 00000000000 11746507402 020066 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/models.py 0000664 0001750 0001750 00000004052 11737026766 021735 0 ustar lukasz lukasz 0000000 0000000 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
# the GNU Lesser General Public License version 3 (see the file LICENSE).
from django.core import serializers
from django.db import models
from django.utils import simplejson
from django.db.models.fields.files import FileField
class AuditLog(models.Model):
"""
Records of all changes made via Django admin interface.
"""
username = models.CharField(max_length=255)
user_id = models.IntegerField()
model = models.CharField(max_length=255)
change = models.CharField(max_length=100)
representation = models.CharField(max_length=255)
values = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
@classmethod
def create(cls, user, obj, change, new_object=None):
assert change in ['create', 'update', 'delete']
values = serializers.serialize("json", [obj])
# json[0] is for removing outside list, this serialization is only for
# complete separate objects, the list is unnecessary
json = simplejson.loads(values)[0]
if new_object:
values_new = serializers.serialize("json", [new_object])
json_new = simplejson.loads(values_new)[0]
json = {'new': json_new, 'old': json}
if change == 'delete':
file_fields = [f for f in obj._meta.fields
if isinstance(f, FileField)]
if len(file_fields) > 0:
json['files'] = {}
for file_field in file_fields:
field_name = file_field.name
file = getattr(obj, field_name)
if file.name:
json['files'][file.name] = file.read().encode('base64')
values_pretty = simplejson.dumps(json, indent=2, sort_keys=True)
return cls.objects.create(
username=user.username,
user_id=user.id,
model=str(obj._meta),
values=values_pretty,
representation=unicode(obj),
change=change,
)
django-adminaudit-0.3.2/adminaudit/templates/ 0000775 0001750 0001750 00000000000 11746507402 022064 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/templates/admin/ 0000775 0001750 0001750 00000000000 11746507402 023154 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/templates/admin/base_site.html 0000664 0001750 0001750 00000001542 11740515574 026005 0 ustar lukasz lukasz 0000000 0000000 {% extends "admin/base.html" %}
{# Copyright 2010-2012 Canonical Ltd. This software is licensed under #}
{# the GNU Lesser General Public License version 3 (see the file LICENSE). #}
{% load i18n %}
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
{% block footer %}
{{ block.super }}
{% endblock %}
{% block branding %}
{% trans 'Django administration' %}
{% endblock %}
{% block nav-global %}{% endblock %}
django-adminaudit-0.3.2/adminaudit/templates/admin/adminaudit/ 0000775 0001750 0001750 00000000000 11746507402 025273 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/templates/admin/adminaudit/auditlog/ 0000775 0001750 0001750 00000000000 11746507402 027103 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/templates/admin/adminaudit/auditlog/change_form.html 0000664 0001750 0001750 00000002262 11740767212 032244 0 ustar lukasz lukasz 0000000 0000000 {% extends "admin/change_form.html" %}
{# Copyright 2010-2012 Canonical Ltd. This software is licensed under #}
{# the GNU Lesser General Public License version 3 (see the file LICENSE). #}
{% load i18n %}
{% block title %}View audit log| {% trans 'Django site admin' %}{% endblock %}
{% block content_title %}View audit log
{% endblock %}
{% block content %}
User |
{{ original.user }} |
Model |
{{ original.model }} |
Representation: |
{{ original.representation }} |
Change |
{{ original.change }} |
Change date |
{{ original.created_at }} |
Change values |
New values |
Old values |
{{ new }} |
{{ old }} |
|
{% endblock %}
django-adminaudit-0.3.2/adminaudit/templates/adminaudit/ 0000775 0001750 0001750 00000000000 11746507402 024203 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/templates/adminaudit/report.txt 0000664 0001750 0001750 00000001055 11740515574 026263 0 ustar lukasz lukasz 0000000 0000000 {# Copyright 2010-2012 Canonical Ltd. This software is licensed under #}
{# the GNU Lesser General Public License version 3 (see the file LICENSE). #}
{% autoescape off %}
Following is the summary of all changes made to the database via admin interface.
{% for user in users %}
{{ user.caption }}
{{ user.underline }}
{% for auditlog in user.auditlogs %}{{ auditlog.created_at }}: {{ auditlog.change }} {{ auditlog.model }} ({{ auditlog.representation }})
{% endfor %}
{% endfor %}
This report was generated for {{ report_for_date }}.
{% endautoescape %}
django-adminaudit-0.3.2/adminaudit/management/ 0000775 0001750 0001750 00000000000 11746507402 022202 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/management/commands/ 0000775 0001750 0001750 00000000000 11746507402 024003 5 ustar lukasz lukasz 0000000 0000000 django-adminaudit-0.3.2/adminaudit/management/commands/adminaudit_email_report.py 0000664 0001750 0001750 00000002607 11740515573 031245 0 ustar lukasz lukasz 0000000 0000000 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
# the GNU Lesser General Public License version 3 (see the file LICENSE).
from operator import itemgetter
from django.conf import settings
from django.template import Context
from django.template.loader import get_template
from django.core.mail import send_mail
from adminaudit.management.commands import AdminAuditBaseCommand
class Command(AdminAuditBaseCommand):
help = "Send out an email reporting the latest admin tasks performed."
def handle(self, *args, **options):
template = get_template("adminaudit/report.txt")
recipients = getattr(settings, 'ADMINAUDIT_EMAILS_RECIPIENTS',
map(itemgetter(1), settings.ADMINS))
subject = getattr(settings, 'ADMINAUDIT_SUMMARY_SUBJECT',
'Admin Audit Summary')
from_email = getattr(settings, 'ADMINAUDIT_EMAIL_FROM',
settings.DEFAULT_FROM_EMAIL)
if not recipients:
print "No admin audit summary recipients configured."
context = self.context_data()
for recipient in recipients:
context['recipient'] = recipient
print "Sending e-email to", recipient,
send_mail(subject, template.render(Context(context)),
from_email, [recipient], fail_silently=False)
print " done"
django-adminaudit-0.3.2/adminaudit/management/commands/adminaudit_report.py 0000664 0001750 0001750 00000001076 11740515573 030075 0 ustar lukasz lukasz 0000000 0000000 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
# the GNU Lesser General Public License version 3 (see the file LICENSE).
from django.template import Context
from django.template.loader import get_template
from adminaudit.management.commands import AdminAuditBaseCommand
class Command(AdminAuditBaseCommand):
help = "Report the latest admin tasks performed."
def handle(self, *args, **kwargs):
template = get_template("adminaudit/report.txt")
context = Context(self.context_data())
print template.render(context)
django-adminaudit-0.3.2/adminaudit/management/commands/__init__.py 0000664 0001750 0001750 00000002312 11740515573 026114 0 ustar lukasz lukasz 0000000 0000000 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
# the GNU Lesser General Public License version 3 (see the file LICENSE).
from datetime import date, timedelta, time, datetime
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from adminaudit.models import AuditLog
class AdminAuditBaseCommand(BaseCommand):
def data_for_user(self, user_id, auditlogs):
username = User.objects.get(pk=user_id).username
caption = "{0}'s changes".format(username)
return {
'auditlogs': auditlogs.filter(user_id=user_id),
'caption': caption,
'underline': "=" * len(caption),
}
def report_date(self):
return date.today() - timedelta(days=1)
def context_data(self):
yesterday = self.report_date()
auditlogs = AuditLog.objects.filter(created_at__range=(
yesterday, datetime.combine(yesterday, time.max)))
users = []
for value in auditlogs.values('user_id').distinct():
users.append(self.data_for_user(value['user_id'], auditlogs))
return {
'report_for_date': yesterday,
'users': users,
}
django-adminaudit-0.3.2/adminaudit/management/__init__.py 0000664 0001750 0001750 00000000221 11737027306 024306 0 ustar lukasz lukasz 0000000 0000000 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
# the GNU Lesser General Public License version 3 (see the file LICENSE).
django-adminaudit-0.3.2/adminaudit/tests.py 0000664 0001750 0001750 00000030256 11746506121 021605 0 ustar lukasz lukasz 0000000 0000000 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
# the GNU Lesser General Public License version 3 (see the file LICENSE).
import os.path
import sys
from cStringIO import StringIO
from random import choice
from string import ascii_letters
from datetime import date, timedelta
from django.test import TestCase
from django.db.models import Model
from django.conf import settings
from django.core import mail
from django.core.files import File
from django.contrib.admin import ModelAdmin, site
from django.contrib.auth.models import User, Permission
from django.utils import simplejson
from mock import Mock, patch
from adminaudit import audit_install
from adminaudit.models import AuditLog
from adminaudit.management.commands.adminaudit_email_report import (
Command as EmailCommand)
from adminaudit.management.commands.adminaudit_report import (
Command as CLICommand)
from example_app.models import Post
class BaseTestCase(TestCase):
def setUp(self):
User.objects.filter(username='test').delete()
AuditLog.objects.all().delete()
self.user = User.objects.create(username='test', is_active=True,
is_staff=True, is_superuser=True)
self.user.set_password('test')
self.user.save()
self.client.login(username='test', password='test')
class AuditAdminEmailReportTestCase(TestCase):
def create_and_log_in_user(self, **kwargs):
defaults = {
'username': "".join(choice(ascii_letters) for x in range(5)),
'is_staff': True,
}
defaults.update(kwargs)
user = User.objects.create(**defaults)
user.set_password('test')
user.save()
# Give access to AuditLog and Post
for perm in ['change_auditlog', 'add_post', 'change_post',
'delete_post']:
user.user_permissions.add(Permission.objects.get(codename=perm))
user.save()
self.client.login(username=user.username, password='test')
return user
def test_when_logging_in_to_admin_you_can_see_audit_log_objects(self):
self.create_and_log_in_user()
r = self.client.get('/admin/')
self.assertContains(r, 'Audit logs')
r = self.client.get('/admin/adminaudit/')
self.assertEqual(200, r.status_code)
def test_even_superuser_can_not_change_audit_log_object(self):
user = self.create_and_log_in_user(is_superuser=True)
auditlog_id = AuditLog.create(user, user, 'create').id
url = '/admin/adminaudit/auditlog/{0}/'.format(auditlog_id)
r = self.client.post(url, {
'values': 'test',
'representation': 'test',
'change': 'create',
'model': 'model',
'user': user.pk,
})
auditlog = AuditLog.objects.get(pk=auditlog_id)
self.assertNotEqual(auditlog.values, 'test')
self.assertEquals(404, r.status_code)
def test_even_superuser_can_not_delete_audit_log_objects(self):
user = self.create_and_log_in_user(is_superuser=True)
auditlog_id = AuditLog.create(user, user, 'create').id
url = '/admin/adminaudit/auditlog/{0}/delete/'.format(auditlog_id)
r = self.client.post(url, {'post': 'yes'})
self.assertEquals(AuditLog.objects.filter(pk=auditlog_id).count(), 1)
self.assertEquals(404, r.status_code)
def test_js_for_removing_change_link_from_index_page_is_present(self):
self.create_and_log_in_user(is_superuser=True)
r = self.client.get('/admin/')
self.assertContains(r, 'a.parentNode.removeChild(a)')
class AdminIntegrationTestCase(BaseTestCase):
def assertOneLogCreated(self, change):
logs = AuditLog.objects.filter(username=self.user.username)
self.assertEquals(1, logs.count())
log = logs[0]
self.assertEquals(change, log.change)
def test_adding_new_object_via_admin_interface_is_saved_by_audit(self):
r = self.client.post('/admin/auth/user/add/', {
'username': "test_user",
'password1': "test",
'password2': "test",
})
# Redirect after object creation
self.assertEquals(302, r.status_code)
self.assertOneLogCreated('create')
def test_changing_object_via_admin_is_saved_by_audit(self):
r = self.client.get('/admin/auth/user/{0}/'.format(self.user.pk))
data = r.context['adminform'].form.initial
data.update({
'first_name': "First",
'last_login_0': str(data['last_login'].date()),
'last_login_1': data['last_login'].strftime("%H:%M:%S"),
'date_joined_0': str(data['date_joined'].date()),
'date_joined_1': data['date_joined'].strftime("%H:%M:%S"),
})
r = self.client.post(
'/admin/auth/user/{0}/'.format(self.user.pk), data)
self.assertEquals(302, r.status_code)
self.assertOneLogCreated('update')
def test_deleting_object_via_admin_is_saved_by_audit(self):
user = User.objects.create_user('newuser', 'new@example.com')
r = self.client.post('/admin/auth/user/{0}/delete/'.format(user.pk), {
'post': 'yes',
})
self.assertEquals(302, r.status_code)
self.assertOneLogCreated('delete')
def test_cascading_deleted_objects_also_receive_audit_log(self):
user = User.objects.create_user('newuser', 'new@example.com')
Post.objects.create(author=user, title='foo', body='bar')
self.client.post('/admin/auth/user/{0}/delete/'.format(user.pk), {
'post': 'yes',
})
# Check that delete cascaded to the related Post object
self.assertEqual(0, Post.objects.count())
# Check that an AuditLog object was created for each
self.assertEqual(2, AuditLog.objects.count())
def test_nested_cascading_deleted_objects(self):
user = User.objects.create_user('newuser', 'new@example.com')
post1 = Post.objects.create(author=user, title='foo', body='bar')
Post.objects.create(author=user, title='bar', body='baz',
related=post1)
self.client.post('/admin/auth/user/{0}/delete/'.format(user.pk), {
'post': 'yes',
})
# Check that delete cascaded to the related Post objects
self.assertEqual(0, Post.objects.count())
# Check that an AuditLog object was created for each
self.assertEqual(3, AuditLog.objects.count())
class AuditLogAdminChangeViewTestCase(BaseTestCase):
def test_display_change_view_for_create_log(self):
auditlog = AuditLog.create(self.user, self.user, 'create')
r = self.client.get(
'/admin/adminaudit/auditlog/{0}/'.format(auditlog.pk))
self.assertContains(r, 'test')
self.assertContains(r, 'auth.user')
self.assertTrue(r.context['new'])
self.assertFalse(r.context['old'])
def test_display_change_view_for_update_log(self):
auditlog = AuditLog.create(self.user, self.user, 'update', self.user)
r = self.client.get(
'/admin/adminaudit/auditlog/{0}/'.format(auditlog.pk))
self.assertTrue(r.context['new'])
self.assertTrue(r.context['old'])
def test_display_change_view_for_delete_log(self):
auditlog = AuditLog.create(self.user, self.user, 'delete')
r = self.client.get(
'/admin/adminaudit/auditlog/{0}/'.format(auditlog.pk))
self.assertFalse(r.context['new'])
self.assertTrue(r.context['old'])
def test_display_change_list(self):
AuditLog.create(self.user, self.user, 'delete')
r = self.client.get('/admin/adminaudit/auditlog/')
self.assertNotContains(r, '