Skip to content

Commit

Permalink
0.1.4 release. Fixes tests and model logic.
Browse files Browse the repository at this point in the history
  - Fixes counters: always enabled (would break migrations)
  - Adds `add_log_user()` and `add_reference()` helper methods
  - Bumps coverage to 100%
  - Improved README
  • Loading branch information
achedeuzot committed Nov 25, 2016
1 parent 86a4bac commit eaf7a7b
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 100 deletions.
38 changes: 34 additions & 4 deletions README.rst
Expand Up @@ -8,9 +8,11 @@ Django CommandLog - Django Command logging
.. image:: https://coveralls.io/repos/github/achedeuzot/django-commandlog/badge.svg?branch=master
:target: https://coveralls.io/github/achedeuzot/django-commandlog?branch=master

Django CommandLog adds the feature of logging the django management commands into the database so it's available in the admin interface.

We've been using this to check the result of scheduled management commands directly through the admin interface. It works by copying the stream of ``stdout`` and ``stderr``.

Django CommandLog adds the possibility to log django management commands. The log is then available in the admin interface.
The decorator also adds some helper methods, see below.

Quick start
-----------
Expand All @@ -27,9 +29,11 @@ Quick start
2. Run `python manage.py migrate` to add the tables of `django_commandlog` to your database.

3. To log an admin command, add the `@command_log` decorator above the class. It currently
supports only custom management commands. If you wish to add this to default django manage commands
you'll have to create a child class with the decorator. Pull/Merge requests are welcome with a fix for this. Example below:
3. To log an admin command, add the `@command_log` decorator above the class
(see example below). Thus, it currently supports only custom management
commands. If you wish to add this to default django manage commands you'll
have to create a child class with the decorator. Pull/Merge requests
are welcome with a fix for this.

.. code-block:: python
Expand All @@ -39,6 +43,32 @@ you'll have to create a child class with the decorator. Pull/Merge requests are
def handle(self, *args, **options):
...
Configuration
-------------

There are currently no configuration values.

Helper methods
--------------
When your class is decorated with ``@command_log``, you have access to additional methods.

CommandLog includes counters for basic CRUD operations (Create, Read, Update, Delete) which can be used
through helper methods provided:

.. code-block:: python
add_created(10)
add_read(30)
add_updated(20)
add_deleted(30)
add_errors(14)
.. code-block:: python
reference
imported_by_user
imported_by_str
Requirements
------------

Expand Down
2 changes: 1 addition & 1 deletion django_commandlog/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-

__version__ = '0.1.3'
__version__ = '0.1.4'

default_app_config = 'django_commandlog.apps.CommandLogConfig'
14 changes: 6 additions & 8 deletions django_commandlog/admin.py
Expand Up @@ -9,23 +9,21 @@

class CommandLogAdmin(admin.ModelAdmin):

list_display = ['__str__', 'start_at', 'end_at', 'duration', 'success', 'reference']
list_display = ['__str__', 'reference', 'start_at', 'end_at', 'duration', 'success', 'total_crud']
list_filter = ['command_name', 'reference']
readonly_fields = ['uuid', 'command_name', 'reference',
'start_at', 'end_at',
'success',
'raw_output', 'stdout', 'stderr',
'imported_by_user', 'imported_by_str', ]
if ENABLE_COUNTER_FIELDS:
readonly_fields += ['created', 'read', 'updated', 'deleted', 'errors']
'imported_by_user', 'imported_by_str',
'created', 'read', 'updated', 'deleted', 'errors']

@staticmethod
def duration(obj):
return obj.get_duration()

if ENABLE_COUNTER_FIELDS:
@staticmethod
def total_crud(obj):
return obj.get_total_crud()
@staticmethod
def total_crud(obj):
return obj.get_total_crud()

admin.site.register(CommandLog, CommandLogAdmin)
2 changes: 1 addition & 1 deletion django_commandlog/app_settings.py
Expand Up @@ -3,4 +3,4 @@

from django.conf import settings

ENABLE_COUNTER_FIELDS = getattr(settings, 'COMMANDLOG_ENABLE_COUNTER_FIELDS', True)
# SOME_SETTING = getattr(settings, 'COMMANDLOG_SOME_SETTING', True)
78 changes: 48 additions & 30 deletions django_commandlog/decorators.py
Expand Up @@ -5,17 +5,18 @@

from functools import wraps

from django.apps import apps
from django.conf import settings
from django.utils import timezone

from django_commandlog import app_settings
from django_commandlog.helpers import OutputTeeWrapper
from django_commandlog.models import CommandLog


def command_log(cls):

def init__decorator(fn):
@wraps(fn)
@wraps(fn, assigned=('__name__', '__doc__'))
def wrapper(self, *args, **kwargs):
fn(self, *args, **kwargs)
if hasattr(self, '_cmdlog'):
Expand All @@ -30,7 +31,7 @@ def wrapper(self, *args, **kwargs):
return wrapper

def handle_decorator(fn):
@wraps(fn)
@wraps(fn, assigned=('__name__', '__doc__'))
def wrapper(self, *args, **kwargs):
try:
fn(self, *args, **kwargs)
Expand All @@ -41,10 +42,9 @@ def wrapper(self, *args, **kwargs):
raise
finally:
self._cmdlog.end_at = timezone.now()
if app_settings.ENABLE_COUNTER_FIELDS:
self._cmdlog.total = self._cmdlog.created + \
self._cmdlog.updated + \
self._cmdlog.deleted
self._cmdlog.total = self._cmdlog.created + \
self._cmdlog.updated + \
self._cmdlog.deleted
self._cmdlog.save()
return wrapper

Expand All @@ -60,35 +60,33 @@ def _add_cmdlog_func(fn, name):
)
setattr(cls, name, fn)

# Add shortcut functions
if app_settings.ENABLE_COUNTER_FIELDS:
def _add_to_counter(self, field_name, value):
self._cmdlog.__dict__[field_name] += value
self._cmdlog.save()
_add_cmdlog_func(_add_to_counter, '_cmdlog_add_to_counter')

def _add_to_counter(self, field_name, value):
self._cmdlog.__dict__[field_name] += value
self._cmdlog.save()
_add_cmdlog_func(_add_to_counter, '_cmdlog_add_to_counter')

# Add helper function for the 4 basic operation counters
def add_created(self, value=1):
self._cmdlog_add_to_counter('created', value)
_add_cmdlog_func(add_created, 'add_created')
# CRUD helpers
def add_log_created(self, value=1):
self._cmdlog_add_to_counter('created', value)
_add_cmdlog_func(add_log_created, 'add_log_created')

def add_read(self, value=1):
self._cmdlog_add_to_counter('read', value)
_add_cmdlog_func(add_read, 'add_read')
def add_log_read(self, value=1):
self._cmdlog_add_to_counter('read', value)
_add_cmdlog_func(add_log_read, 'add_log_read')

def add_updated(self, value=1):
self._cmdlog_add_to_counter('updated', value)
_add_cmdlog_func(add_updated, 'add_updated')
def add_log_updated(self, value=1):
self._cmdlog_add_to_counter('updated', value)
_add_cmdlog_func(add_log_updated, 'add_log_updated')

def add_deleted(self, value=1):
self._cmdlog_add_to_counter('deleted', value)
_add_cmdlog_func(add_deleted, 'add_deleted')
def add_log_deleted(self, value=1):
self._cmdlog_add_to_counter('deleted', value)
_add_cmdlog_func(add_log_deleted, 'add_log_deleted')

def add_errors(self, value=1):
self._cmdlog_add_to_counter('errors', value)
_add_cmdlog_func(add_errors, 'add_errors')
def add_log_errors(self, value=1):
self._cmdlog_add_to_counter('errors', value)
_add_cmdlog_func(add_log_errors, 'add_log_errors')

# Logging helpers
def write_success(self, msg):
self.stdout.write(self.style.SUCCESS(msg))
_add_cmdlog_func(write_success, 'write_success')
Expand All @@ -105,4 +103,24 @@ def write_error(self, msg):
self.stderr.write(self.style.ERROR(msg))
_add_cmdlog_func(write_error, 'write_error')

# Add reference
def add_log_reference(self, ref):
self._cmdlog.reference = ref
self._cmdlog.save()
_add_cmdlog_func(add_log_reference, 'add_log_reference')

# Add User
def add_log_user(self, user):
UserModel = apps.get_model(settings.AUTH_USER_MODEL)
if isinstance(user, UserModel):
self._cmdlog.imported_by_user = user
self._cmdlog.imported_by_str = str(user)
self._cmdlog.save()
elif isinstance(user, basestring):
self._cmdlog.imported_by_str = user
self._cmdlog.save()
else:
raise ValueError("`add_log_user()` accepts only settings.AUTH_USER_MODEL or string values.")
_add_cmdlog_func(add_log_user, 'add_log_user')

return cls
20 changes: 10 additions & 10 deletions django_commandlog/models.py
Expand Up @@ -28,16 +28,17 @@ class CommandLog(TimestampableModel, models.Model):
# Generic field if you need to sort or filter your commands
reference = models.CharField(max_length=255, editable=False)

imported_by_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, editable=False)
imported_by_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, editable=False,
on_delete=models.SET_NULL)
imported_by_str = models.CharField(max_length=255, editable=False)

if ENABLE_COUNTER_FIELDS:
created = models.BigIntegerField(default=0, editable=False)
read = models.BigIntegerField(default=0, editable=False)
updated = models.BigIntegerField(default=0, editable=False)
deleted = models.BigIntegerField(default=0, editable=False)
# CRUD field counters
created = models.BigIntegerField(default=0, editable=False)
read = models.BigIntegerField(default=0, editable=False)
updated = models.BigIntegerField(default=0, editable=False)
deleted = models.BigIntegerField(default=0, editable=False)

errors = models.BigIntegerField(default=0, editable=False)
errors = models.BigIntegerField(default=0, editable=False)

class Meta:
verbose_name = _('command log')
Expand All @@ -61,9 +62,8 @@ def get_duration(self):
else:
return datetime.timedelta()

if ENABLE_COUNTER_FIELDS:
def get_total_crud(self):
return self.created + self.read + self.updated + self.deleted
def get_total_crud(self):
return self.created + self.read + self.updated + self.deleted


@receiver(pre_save)
Expand Down
2 changes: 2 additions & 0 deletions django_commandlog/tests/__init__.py
Expand Up @@ -12,6 +12,8 @@
'NAME': ':memory:',
}
},
USE_TZ=True,
TIME_ZONE='UTC',
INSTALLED_APPS=('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
Expand Down
43 changes: 43 additions & 0 deletions django_commandlog/tests/tests_admin.py
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function


import datetime
from django.contrib.admin import AdminSite
from django.test import TestCase
from django.utils.timezone import make_aware

from django_commandlog.admin import CommandLogAdmin
from django_commandlog.models import CommandLog


class MockRequest(object):
pass


class MockSuperUser(object):
def has_perm(self, perm):
return True


request = MockRequest()
request.user = MockSuperUser()


class CommandLogAdminTestCase(TestCase):

def setUp(self):
self.site = AdminSite()
self.cmdlog = CommandLog()
self.cmdlog.save()
self.cmdlog.start_at = make_aware(datetime.datetime(year=2016, month=2, day=2, hour=13, minute=37, second=42))
self.cmdlog.end_at = self.cmdlog.start_at + datetime.timedelta(days=1)
self.cmdlog.created = 42
self.cmdlog.deleted = 21
self.cmdlog.save()

def test_log_admin_duration(self):
self.assertEqual(CommandLogAdmin.duration(self.cmdlog), datetime.timedelta(days=1))

def test_log_admin_total_crud(self):
self.assertEqual(CommandLogAdmin.total_crud(self.cmdlog), 63)

0 comments on commit eaf7a7b

Please sign in to comment.