diff --git a/django_journal/actions.py b/django_journal/actions.py
index e5bdbf5..35a88ed 100644
--- a/django_journal/actions.py
+++ b/django_journal/actions.py
@@ -1,13 +1,12 @@
import csv
-
-from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse
from django.utils.encoding import force_text
-
+from django.utils.translation import ugettext_lazy as _
from . import models
+
def export_as_csv_generator(queryset):
header = ['time', 'tag', 'message']
tags = set(models.Tag.objects.filter(objectdata__journal__in=queryset).values_list('name', flat=True))
@@ -15,13 +14,13 @@ def export_as_csv_generator(queryset):
tags.add('%s__id' % tag)
tags |= set(models.Tag.objects.filter(stringdata__journal__in=queryset).values_list('name', flat=True))
extra_headers = list(sorted(tags))
- yield header+extra_headers
+ yield header + extra_headers
for journal in queryset:
row = {
- 'time': journal.time.isoformat(' '),
- 'tag': force_text(journal.tag.name),
- 'message': force_text(journal),
- }
+ 'time': journal.time.isoformat(' '),
+ 'tag': force_text(journal.tag.name),
+ 'message': force_text(journal),
+ }
for stringdata in journal.stringdata_set.all():
row_name = stringdata.tag.name.encode('utf-8')
row[force_text(row_name)] = force_text(stringdata.content)
@@ -34,6 +33,7 @@ def export_as_csv_generator(queryset):
row[row_name] = force_text(objectdata.content_object)
yield row
+
def export_as_csv(modeladmin, request, queryset):
"""
CSV export for journal
@@ -47,4 +47,6 @@ def export_as_csv(modeladmin, request, queryset):
for row in l:
writer.writerow(row)
return response
-export_as_csv.short_description = _(u"Export CSV file")
+
+
+export_as_csv.short_description = _("Export CSV file")
diff --git a/django_journal/admin.py b/django_journal/admin.py
index 638faec..2c83ed7 100644
--- a/django_journal/admin.py
+++ b/django_journal/admin.py
@@ -2,47 +2,48 @@ from string import Formatter
import django.contrib.admin as admin
from django.contrib.contenttypes.models import ContentType
-from django.utils.html import format_html, escape, mark_safe
from django.db import models
+from django.urls import NoReverseMatch, reverse
from django.utils.encoding import force_text
+from django.utils.html import escape, format_html, mark_safe
from django.utils.translation import ugettext_lazy as _
-from django.urls import reverse, NoReverseMatch
-from .models import Journal, Tag, ObjectData, StringData
from .actions import export_as_csv
+from .models import Journal, ObjectData, StringData, Tag
class ModelAdminFormatter(Formatter):
- def __init__(self, model_admin=None, filter_link=True,
- object_link=True):
+ def __init__(self, model_admin=None, filter_link=True, object_link=True):
self.filter_link = filter_link
self.object_link = object_link
self.model_admin = model_admin
- super(ModelAdminFormatter, self).__init__()
+ super().__init__()
def build_object_link(self, value):
content_type = ContentType.objects.get_for_model(value.__class__)
- url = u'{0}:{1}_{2}_change'.format(self.model_admin.admin_site.name,
- content_type.app_label, content_type.model)
+ url = '{}:{}_{}_change'.format(
+ self.model_admin.admin_site.name, content_type.app_label, content_type.model
+ )
try:
url = reverse(url, args=(value.pk,))
except NoReverseMatch:
- return u''
- return u''.format(escape(url))
+ return ''
+ return f''
def format_field(self, value, format_spec):
if isinstance(value, models.Model):
res = ''
if self.filter_link:
content_type = ContentType.objects.get_for_model(value.__class__)
- res = u'{2}'.format(
- content_type.id, value.pk, escape(force_text(value)))
+ res = '{}'.format(
+ content_type.id, value.pk, escape(force_text(value))
+ )
else:
res = escape(force_text(value))
if self.object_link:
res += self.build_object_link(value)
return res
- return escape(super(ModelAdminFormatter, self).format_field(value, format_spec))
+ return escape(super().format_field(value, format_spec))
class ObjectDataInlineAdmin(admin.TabularInline):
@@ -52,6 +53,7 @@ class ObjectDataInlineAdmin(admin.TabularInline):
extra = 0
max_num = 0
+
class StringDataInlineAdmin(admin.TabularInline):
model = StringData
fields = ('tag', 'content')
@@ -59,91 +61,98 @@ class StringDataInlineAdmin(admin.TabularInline):
extra = 0
max_num = 0
+
class JournalAdmin(admin.ModelAdmin):
list_display = ('time', '_tag', 'user', 'ip', 'message_for_list')
list_filter = ('tag',)
fields = ('time', 'tag', 'user', 'ip', 'message_for_change')
readonly_fields = fields
inlines = (
- ObjectDataInlineAdmin,
- StringDataInlineAdmin,
+ ObjectDataInlineAdmin,
+ StringDataInlineAdmin,
)
date_hierarchy = 'time'
- search_fields = ('message','tag__name','time')
- actions = [ export_as_csv ]
+ search_fields = ('message', 'tag__name', 'time')
+ actions = [export_as_csv]
class Media:
css = {
- 'all': ('journal/css/journal.css',),
+ 'all': ('journal/css/journal.css',),
}
def queryset(self, request):
'''Get as much data as possible using the fewest requests possible.'''
- qs = super(JournalAdmin, self).queryset(request)
- qs = qs.select_related('tag', 'template') \
- .prefetch_related('objectdata_set__content_type',
- 'stringdata_set', 'objectdata_set__tag',
- 'stringdata_set__tag', 'objectdata_set__content_object')
+ qs = super().queryset(request)
+ qs = qs.select_related('tag', 'template').prefetch_related(
+ 'objectdata_set__content_type',
+ 'stringdata_set',
+ 'objectdata_set__tag',
+ 'stringdata_set__tag',
+ 'objectdata_set__content_object',
+ )
return qs
def lookup_allowed(self, key, *args, **kwargs):
return True
def _tag(self, entry):
- name = entry.tag.name.replace(u'-', u'\u2011')
- res = format_html('{1}',
- escape(entry.tag.id), escape(name))
+ name = entry.tag.name.replace('-', '\u2011')
+ res = format_html('{1}', escape(entry.tag.id), escape(name))
return res
+
_tag.short_description = _('tag')
def ip(self, entry):
'''Search and return any associated stringdata whose tag is "ip"'''
for stringdata in entry.stringdata_set.all():
if stringdata.tag.name == 'ip':
- return format_html('{ip}',
- tag_id=stringdata.tag.id, ip=stringdata.content)
+ return format_html(
+ '{ip}',
+ tag_id=stringdata.tag.id,
+ ip=stringdata.content,
+ )
return _('None')
+
ip.short_description = _('IP')
def user(self, entry):
'''Search and return any associated objectdata whose tag is "user"'''
for objectdata in entry.objectdata_set.all():
if objectdata.tag.name == 'user':
- return format_html(self.object_filter_link(objectdata) + \
- self.object_link(objectdata))
+ return format_html(self.object_filter_link(objectdata) + self.object_link(objectdata))
return _('None')
+
user.short_description = _('User')
def object_filter_link(self, objectdata):
if objectdata.content_object is not None:
caption = force_text(objectdata.content_object)
else:
- caption = _(u'').format(
- content_type=objectdata.content_type,
- object_id=objectdata.object_id)
- return u'{2}'.format(
- objectdata.content_type_id,
- objectdata.object_id,
- escape(caption))
+ caption = _('').format(
+ content_type=objectdata.content_type, object_id=objectdata.object_id
+ )
+ return '{}'.format(
+ objectdata.content_type_id, objectdata.object_id, escape(caption)
+ )
def object_link(self, obj_data):
if obj_data.content_object is None:
- return u''
- url = u'{0}:{1}_{2}_change'.format(self.admin_site.name,
- obj_data.content_type.app_label,
- obj_data.content_type.model)
+ return ''
+ url = '{}:{}_{}_change'.format(
+ self.admin_site.name, obj_data.content_type.app_label, obj_data.content_type.model
+ )
try:
url = reverse(url, args=(obj_data.object_id,))
except NoReverseMatch:
return ''
- return u''.format(url)
+ return f''
def message_for_change(self, entry):
ctx = entry.message_context()
formatter = ModelAdminFormatter(model_admin=self, filter_link=False)
message = formatter.format(escape(entry.template.content), **ctx)
return format_html('{}', mark_safe(message))
+
message_for_change.short_description = _('Message')
def message_for_list(self, entry):
@@ -151,8 +160,10 @@ class JournalAdmin(admin.ModelAdmin):
formatter = ModelAdminFormatter(model_admin=self)
message = formatter.format(entry.template.content, **ctx)
return format_html('{}', mark_safe(message))
+
message_for_list.short_description = _('Message')
message_for_list.admin_order_field = 'message'
+
admin.site.register(Journal, JournalAdmin)
admin.site.register(Tag)
diff --git a/django_journal/decorator.py b/django_journal/decorator.py
index 824227a..cd9237a 100644
--- a/django_journal/decorator.py
+++ b/django_journal/decorator.py
@@ -1,10 +1,12 @@
from functools import wraps
-from django.db import transaction, DEFAULT_DB_ALIAS
+
+from django.db import DEFAULT_DB_ALIAS, transaction
if hasattr(transaction, 'atomic'):
atomic = transaction.atomic
else:
- class Transaction(object):
+
+ class Transaction:
sid = None
def __init__(self, using=None):
@@ -41,8 +43,8 @@ else:
def wrapper(*args, **kwargs):
with self.__class__(using=self.using):
return func(*args, **kwargs)
- return wrapper
+ return wrapper
def atomic(using=None):
"""
@@ -56,5 +58,3 @@ else:
if callable(using):
return Transaction(DEFAULT_DB_ALIAS)(using)
return Transaction(using)
-
-
diff --git a/django_journal/journal.py b/django_journal/journal.py
index 69892a8..a05c127 100644
--- a/django_journal/journal.py
+++ b/django_journal/journal.py
@@ -1,8 +1,8 @@
import logging
+import django.db.models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
-import django.db.models
from django.utils.encoding import force_text
from .decorator import atomic
@@ -11,32 +11,31 @@ from .models import Journal, Tag, Template
def unicode_truncate(s, length, encoding='utf-8'):
- '''Truncate an unicode string so that its UTF-8 encoding is less than
- length.'''
+ """Truncate an unicode string so that its UTF-8 encoding is less than
+ length."""
encoded = s.encode(encoding)[:length]
return encoded.decode(encoding, 'ignore')
@atomic
def record(tag, template, using=None, **kwargs):
- '''Record an event in the journal. The modification is done inside the
- current transaction.
+ """Record an event in the journal. The modification is done inside the
+ current transaction.
- tag:
- a string identifier giving the type of the event
- tpl:
- a format string to describe the event
- kwargs:
- a mapping of object or data to interpolate in the format string
- '''
+ tag:
+ a string identifier giving the type of the event
+ tpl:
+ a format string to describe the event
+ kwargs:
+ a mapping of object or data to interpolate in the format string
+ """
template = force_text(template)
tag = Tag.objects.using(using).get_cached(name=tag)
template = Template.objects.using(using).get_cached(content=template)
try:
message = template.content.format(**kwargs)
except (KeyError, IndexError) as e:
- raise JournalException(
- 'Missing variable for the template message', template, e)
+ raise JournalException('Missing variable for the template message', template, e)
try:
logger = logging.getLogger('django.journal.%s' % tag)
if tag.name == 'error' or tag.name.startswith('error-'):
@@ -49,17 +48,19 @@ def record(tag, template, using=None, **kwargs):
try:
logging.getLogger('django.journal').exception('Unable to log msg')
except:
- pass # we tried, really, we tried
- journal = Journal.objects.using(using).create(tag=tag, template=template,
- message=unicode_truncate(message, 128))
+ pass # we tried, really, we tried
+ journal = Journal.objects.using(using).create(
+ tag=tag, template=template, message=unicode_truncate(message, 128)
+ )
for name, value in kwargs.items():
if value is None:
continue
tag = Tag.objects.using(using).get_cached(name=name)
if isinstance(value, django.db.models.Model):
journal.objectdata_set.create(
- tag=tag, content_type=ContentType.objects.db_manager(using).get_for_model(value),
- object_id=value.pk
+ tag=tag,
+ content_type=ContentType.objects.db_manager(using).get_for_model(value),
+ object_id=value.pk,
)
else:
journal.stringdata_set.create(tag=tag, content=force_text(value))
@@ -67,13 +68,13 @@ def record(tag, template, using=None, **kwargs):
def error_record(tag, tpl, **kwargs):
- '''Records error events.
+ """Records error events.
- You must use this function when logging error events. It uses another
- database alias than the default one to be immune to transaction rollback
- when logging in the middle of a transaction which is going to
- rollback.
- '''
+ You must use this function when logging error events. It uses another
+ database alias than the default one to be immune to transaction rollback
+ when logging in the middle of a transaction which is going to
+ rollback.
+ """
if kwargs.get('using') is None:
kwargs['using'] = getattr(settings, 'JOURNAL_DB_FOR_ERROR_ALIAS', 'default')
diff --git a/django_journal/managers.py b/django_journal/managers.py
index 10aea73..86f2052 100644
--- a/django_journal/managers.py
+++ b/django_journal/managers.py
@@ -1,6 +1,6 @@
from django.contrib.contenttypes.models import ContentType
+from django.db.models import Manager, Q
from django.db.models.query import QuerySet
-from django.db.models import Q, Manager
class CachedQuerySet(QuerySet):
@@ -27,34 +27,29 @@ class JournalQuerySet(QuerySet):
'''Return Journal records linked to this object.'''
content_type = ContentType.objects.get_for_model(obj)
if tag is None:
- return self.filter(objectdata__content_type=content_type,
- objectdata__object_id=obj.pk)
+ return self.filter(objectdata__content_type=content_type, objectdata__object_id=obj.pk)
else:
return self.filter(
- objectdata__tag__name=tag,
- objectdata__content_type=content_type,
- objectdata__object_id=obj.pk)
+ objectdata__tag__name=tag, objectdata__content_type=content_type, objectdata__object_id=obj.pk
+ )
def for_objects(self, objects):
- '''Return journal records linked to any of this objects.
+ """Return journal records linked to any of this objects.
- All objects must have the same model.
- '''
+ All objects must have the same model.
+ """
if not objects:
return self.none()
- content_types = [ ContentType.objects.get_for_model(obj)
- for obj in objects ]
+ content_types = [ContentType.objects.get_for_model(obj) for obj in objects]
if len(set(content_types)) != 1:
raise ValueError('objects must have of the same content type')
- pks = [ obj.pk for obj in objects ]
- return self.filter(
- objectdata__content_type=content_types[0],
- objectdata__object_id__in=pks)
+ pks = [obj.pk for obj in objects]
+ return self.filter(objectdata__content_type=content_types[0], objectdata__object_id__in=pks)
def for_tag(self, tag):
- '''Returns Journal records linked to this tag by their own tag or
- the tag on their data records.
- '''
+ """Returns Journal records linked to this tag by their own tag or
+ the tag on their data records.
+ """
from . import models
if not isinstance(tag, models.Tag):
@@ -64,17 +59,22 @@ class JournalQuerySet(QuerySet):
return self.none()
# always remember: multiple join (OR in WHERE) produces duplicate
# lines ! Use .distinct() for safety.
- return self.filter(Q(tag=tag)|
- Q(objectdata__tag=tag)|
- Q(stringdata__tag=tag)) \
- .distinct()
+ return self.filter(Q(tag=tag) | Q(objectdata__tag=tag) | Q(stringdata__tag=tag)).distinct()
class JournalManager(Manager.from_queryset(JournalQuerySet)):
def get_query_set(self):
- return super(JournalManager, self).get_query_set() \
- .prefetch_related('objectdata_set__content_type',
- 'stringdata_set', 'objectdata_set__tag',
- 'stringdata_set__tag', 'objectdata_set__content_object',
- 'tag', 'template') \
- .select_related('tag', 'template')
+ return (
+ super()
+ .get_query_set()
+ .prefetch_related(
+ 'objectdata_set__content_type',
+ 'stringdata_set',
+ 'objectdata_set__tag',
+ 'stringdata_set__tag',
+ 'objectdata_set__content_object',
+ 'tag',
+ 'template',
+ )
+ .select_related('tag', 'template')
+ )
diff --git a/django_journal/middleware.py b/django_journal/middleware.py
index c3b8be0..096b1f1 100644
--- a/django_journal/middleware.py
+++ b/django_journal/middleware.py
@@ -1,29 +1,33 @@
from django.utils.deprecation import MiddlewareMixin
+
from django_journal import journal
class JournalMiddleware(MiddlewareMixin):
- '''Add record and error_record methods to the request object to log
- current user and current REMOTE_ADRESS.
+ """Add record and error_record methods to the request object to log
+ current user and current REMOTE_ADRESS.
- It must be setup after the auth middleware.
- '''
+ It must be setup after the auth middleware.
+ """
def process_request(self, request):
user = getattr(request, 'user', None)
ip = request.META.get('REMOTE_ADDR', None)
+
def record(tag, template, using=None, **kwargs):
if 'user' not in kwargs:
kwargs['user'] = user
if 'ip' not in kwargs:
kwargs['ip'] = ip
- journal.record(tag, template, using=using,**kwargs)
+ journal.record(tag, template, using=using, **kwargs)
+
def error_record(tag, template, using=None, **kwargs):
if 'user' not in kwargs:
kwargs['user'] = user
if 'ip' not in kwargs:
kwargs['ip'] = ip
journal.error_record(tag, template, using=using, **kwargs)
+
request.record = record
request.error_record = error_record
return None
diff --git a/django_journal/migrations/0001_initial.py b/django_journal/migrations/0001_initial.py
index 8a5def2..9a2f303 100644
--- a/django_journal/migrations/0001_initial.py
+++ b/django_journal/migrations/0001_initial.py
@@ -1,8 +1,5 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
@@ -15,7 +12,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Journal',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('time', models.DateTimeField(auto_now_add=True, verbose_name='time', db_index=True)),
('message', models.CharField(max_length=128, verbose_name='message', db_index=True)),
],
@@ -28,10 +28,23 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ObjectData',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('object_id', models.PositiveIntegerField(verbose_name='object id', db_index=True)),
- ('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE)),
- ('journal', models.ForeignKey(verbose_name='journal entry', to='django_journal.Journal', on_delete=models.CASCADE)),
+ (
+ 'content_type',
+ models.ForeignKey(
+ verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE
+ ),
+ ),
+ (
+ 'journal',
+ models.ForeignKey(
+ verbose_name='journal entry', to='django_journal.Journal', on_delete=models.CASCADE
+ ),
+ ),
],
options={
'verbose_name': 'linked object',
@@ -40,9 +53,17 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='StringData',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('content', models.TextField(verbose_name='content')),
- ('journal', models.ForeignKey(verbose_name='journal entry', to='django_journal.Journal', on_delete=models.CASCADE)),
+ (
+ 'journal',
+ models.ForeignKey(
+ verbose_name='journal entry', to='django_journal.Journal', on_delete=models.CASCADE
+ ),
+ ),
],
options={
'verbose_name': 'linked text string',
@@ -51,7 +72,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Tag',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('name', models.CharField(unique=True, max_length=32, verbose_name='name', db_index=True)),
],
options={
@@ -62,7 +86,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Template',
fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ (
+ 'id',
+ models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
+ ),
('content', models.TextField(unique=True, verbose_name='content', db_index=True)),
],
options={
@@ -82,19 +109,25 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='journal',
name='tag',
- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, verbose_name='tag', to='django_journal.Tag'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT, verbose_name='tag', to='django_journal.Tag'
+ ),
),
migrations.AddField(
model_name='journal',
name='template',
- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, verbose_name='template', to='django_journal.Template'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ verbose_name='template',
+ to='django_journal.Template',
+ ),
),
migrations.AlterUniqueTogether(
name='stringdata',
- unique_together=set([('journal', 'tag')]),
+ unique_together={('journal', 'tag')},
),
migrations.AlterUniqueTogether(
name='objectdata',
- unique_together=set([('journal', 'tag')]),
+ unique_together={('journal', 'tag')},
),
]
diff --git a/django_journal/models.py b/django_journal/models.py
index 10840bf..6bbe532 100644
--- a/django_journal/models.py
+++ b/django_journal/models.py
@@ -1,7 +1,7 @@
import string
-from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
+from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
@@ -10,14 +10,14 @@ from . import managers
@python_2_unicode_compatible
class Tag(models.Model):
- '''Tag allows typing event and data linked to events.
+ """Tag allows typing event and data linked to events.
+
+ name:
+ the string identifier of the tag
+ """
- name:
- the string identifier of the tag
- '''
objects = managers.TagManager()
- name = models.CharField(verbose_name=_('name'), max_length=32, unique=True,
- db_index=True)
+ name = models.CharField(verbose_name=_('name'), max_length=32, unique=True, db_index=True)
def __str__(self):
return self.name
@@ -32,14 +32,14 @@ class Tag(models.Model):
@python_2_unicode_compatible
class Template(models.Model):
- '''Template for formatting an event.
+ """Template for formatting an event.
+
+ ex.: Template(
+ content='{user1} gave group {group} to {user2}')
+ """
- ex.: Template(
- content='{user1} gave group {group} to {user2}')
- '''
objects = managers.TemplateManager()
- content = models.TextField(verbose_name=_('content'), unique=True,
- db_index=True)
+ content = models.TextField(verbose_name=_('content'), unique=True, db_index=True)
def __str__(self):
return self.content
@@ -53,26 +53,23 @@ class Template(models.Model):
@python_2_unicode_compatible
class Journal(models.Model):
- '''One line of the journal.
+ """One line of the journal.
- Each recorded event in the journal is a Journal instance.
+ Each recorded event in the journal is a Journal instance.
+
+ time - the time at which the event was recorded
+ tag - the tag giving the type of event
+ template - a format string to present the event
+ message - a simple string representation of the event, computed using
+ the template and associated datas.
+ """
- time - the time at which the event was recorded
- tag - the tag giving the type of event
- template - a format string to present the event
- message - a simple string representation of the event, computed using
- the template and associated datas.
- '''
objects = managers.JournalManager()
- time = models.DateTimeField(verbose_name=_('time'), auto_now_add=True,
- db_index=True)
- tag = models.ForeignKey(Tag, verbose_name=_('tag'),
- on_delete=models.PROTECT)
- template = models.ForeignKey(Template, verbose_name=_('template'),
- on_delete=models.PROTECT)
- message = models.CharField(verbose_name=_('message'), max_length=128,
- db_index=True)
+ time = models.DateTimeField(verbose_name=_('time'), auto_now_add=True, db_index=True)
+ tag = models.ForeignKey(Tag, verbose_name=_('tag'), on_delete=models.PROTECT)
+ template = models.ForeignKey(Template, verbose_name=_('template'), on_delete=models.PROTECT)
+ message = models.CharField(verbose_name=_('message'), max_length=128, db_index=True)
class Meta:
ordering = ('-id',)
@@ -85,8 +82,9 @@ class Journal(models.Model):
if data.content_object is not None:
ctx[data.tag.name] = data.content_object
else:
- ctx[data.tag.name] = u''.format(
- content_type=data.content_type, object_id=data.object_id)
+ ctx[data.tag.name] = ''.format(
+ content_type=data.content_type, object_id=data.object_id
+ )
for data in self.stringdata_set.all():
ctx[data.tag.name] = data.content
for text, field, format_spec, conversion in string.Formatter().parse(self.template.content):
@@ -98,30 +96,29 @@ class Journal(models.Model):
return ctx
def add_object_tag(self, tag_name, obj):
- ObjectData(journal=self,
- tag=Tag.objects.get_cached(name=tag_name),
- content_object=obj).save()
+ ObjectData(journal=self, tag=Tag.objects.get_cached(name=tag_name), content_object=obj).save()
def __str__(self):
ctx = self.message_context()
return self.template.content.format(**ctx)
def __repr__(self):
- return ''.format(
- self.pk, unicode(self.tag).encode('utf-8'),
- unicode(self.message).encode('utf-8'))
+ return ''.format(
+ self.pk, unicode(self.tag).encode('utf-8'), unicode(self.message).encode('utf-8')
+ )
class StringData(models.Model):
- '''String data associated to a recorded event.
+ """String data associated to a recorded event.
+
+ journal:
+ the recorded event
+ tag:
+ the identifier for this data
+ content:
+ the string value of the data
+ """
- journal:
- the recorded event
- tag:
- the identifier for this data
- content:
- the string value of the data
- '''
journal = models.ForeignKey(Journal, verbose_name=_('journal entry'), on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, verbose_name=_('tag'), on_delete=models.CASCADE)
content = models.TextField(verbose_name=_('content'))
@@ -133,27 +130,27 @@ class StringData(models.Model):
@python_2_unicode_compatible
class ObjectData(models.Model):
- '''Object data associated with a recorded event.
+ """Object data associated with a recorded event.
+
+ journal:
+ the recorded event
+ tag:
+ the identifier for this data
+ content_object:
+ the object value of the data
+ """
- journal:
- the recorded event
- tag:
- the identifier for this data
- content_object:
- the object value of the data
- '''
journal = models.ForeignKey(Journal, verbose_name=_('journal entry'), on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, verbose_name=_('tag'), on_delete=models.CASCADE)
- content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE,
- verbose_name=_('content type'))
- object_id = models.PositiveIntegerField(db_index=True,
- verbose_name=_('object id'))
- content_object = GenericForeignKey('content_type',
- 'object_id')
+ content_type = models.ForeignKey(
+ 'contenttypes.ContentType', on_delete=models.CASCADE, verbose_name=_('content type')
+ )
+ object_id = models.PositiveIntegerField(db_index=True, verbose_name=_('object id'))
+ content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
unique_together = (('journal', 'tag'),)
verbose_name = _('linked object')
def __str__(self):
- return u'{0}:{1}:{2}'.format(self.journal.id, self.tag, self.content_object)
+ return f'{self.journal.id}:{self.tag}:{self.content_object}'
diff --git a/setup.py b/setup.py
index 6528369..f740cb2 100755
--- a/setup.py
+++ b/setup.py
@@ -2,12 +2,12 @@
import os
import subprocess
import sys
-
-from setuptools import setup, find_packages
-from setuptools.command.install_lib import install_lib as _install_lib
-from distutils.command.build import build as _build
-from setuptools.command.sdist import sdist
from distutils.cmd import Command
+from distutils.command.build import build as _build
+
+from setuptools import find_packages, setup
+from setuptools.command.install_lib import install_lib as _install_lib
+from setuptools.command.sdist import sdist
class test(Command):
@@ -22,6 +22,7 @@ class test(Command):
def run(self):
import os
+
try:
from django.core.management import call_command
except ImportError:
@@ -45,6 +46,7 @@ class compile_translations(Command):
try:
os.environ.pop('DJANGO_SETTINGS_MODULE', None)
from django.core.management import call_command
+
os.chdir(os.path.realpath('django_journal'))
call_command('compilemessages')
except ImportError:
@@ -80,16 +82,18 @@ class eo_sdist(sdist):
def get_version():
- '''Use the VERSION, if absent generates a version with git describe, if not
- tag exists, take 0.0- and add the length of the commit log.
- '''
+ """Use the VERSION, if absent generates a version with git describe, if not
+ tag exists, take 0.0- and add the length of the commit log.
+ """
if os.path.exists('VERSION'):
- with open('VERSION', 'r') as v:
+ with open('VERSION') as v:
return v.read()
if os.path.exists('.git'):
p = subprocess.Popen(
['git', 'describe', '--dirty=.dirty', '--match=v*'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
result = p.communicate()[0]
if p.returncode == 0:
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
@@ -100,30 +104,27 @@ def get_version():
version = result
return version
else:
- return '0.0.post%s' % len(
- subprocess.check_output(
- ['git', 'rev-list', 'HEAD']).splitlines())
+ return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
return '0.0'
-setup(name='django-journal',
- version=get_version(),
- license='AGPLv3',
- description='Keep a structured -- i.e. not just log strings -- journal'
- ' of events in your applications',
- url='http://dev.entrouvert.org/projects/django-journal/',
- download_url='http://repos.entrouvert.org/django-journal.git/',
- author="Entr'ouvert",
- author_email="info@entrouvert.com",
- packages=find_packages(os.path.dirname(__file__) or '.'),
- include_package_data=True,
- cmdclass={
- 'build': build,
- 'install_lib': install_lib,
- 'compile_translations': compile_translations,
- 'sdist': eo_sdist,
- 'test': test
- },
- install_requires=[
- 'django >= 1.11,<2.3'
- ])
+setup(
+ name='django-journal',
+ version=get_version(),
+ license='AGPLv3',
+ description='Keep a structured -- i.e. not just log strings -- journal' ' of events in your applications',
+ url='http://dev.entrouvert.org/projects/django-journal/',
+ download_url='http://repos.entrouvert.org/django-journal.git/',
+ author="Entr'ouvert",
+ author_email="info@entrouvert.com",
+ packages=find_packages(os.path.dirname(__file__) or '.'),
+ include_package_data=True,
+ cmdclass={
+ 'build': build,
+ 'install_lib': install_lib,
+ 'compile_translations': compile_translations,
+ 'sdist': eo_sdist,
+ 'test': test,
+ },
+ install_requires=['django >= 1.11,<2.3'],
+)
diff --git a/test_settings.py b/test_settings.py
index f0902ed..b1ca36e 100644
--- a/test_settings.py
+++ b/test_settings.py
@@ -1,16 +1,12 @@
INSTALLED_APPS = (
- 'django_journal', 'django.contrib.contenttypes', 'django.contrib.auth',
- 'django.contrib.sessions'
+ 'django_journal',
+ 'django.contrib.contenttypes',
+ 'django.contrib.auth',
+ 'django.contrib.sessions',
)
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.postgresql_psycopg2',
- 'NAME': '_test'
- },
- 'error': {
- 'ENGINE': 'django.db.backends.postgresql_psycopg2',
- 'NAME': '_test'
- }
+ 'default': {'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': '_test'},
+ 'error': {'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': '_test'},
}
SECRET_KEY = "django_tests_secret_key"
diff --git a/tests/test_main.py b/tests/test_main.py
index 766b4cd..4491db9 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,11 +1,10 @@
-from django.test import TestCase
-from django.contrib.auth.models import User, Group
+from django.contrib.auth.models import Group, User
from django.db import transaction
+from django.test import TestCase
from django.utils.encoding import force_text
-
-from django_journal.journal import record, error_record
from django_journal.actions import export_as_csv_generator
+from django_journal.journal import error_record, record
from django_journal.models import Journal, Tag
@@ -15,39 +14,57 @@ class JournalTestCase(TestCase):
self.groups = []
with transaction.atomic():
for i in range(20):
- self.users.append(
- User.objects.create(username='user%s' % i))
+ self.users.append(User.objects.create(username='user%s' % i))
for i in range(20):
- self.groups.append(
- Group.objects.create(name='group%s' % i))
+ self.groups.append(Group.objects.create(name='group%s' % i))
for i in range(20):
record('login', '{user} logged in', user=self.users[i])
for i in range(20):
- record('group-changed', '{user1} gave group {group} to {user2}',
- user1=self.users[i], group=self.groups[i],
- user2=self.users[(i+1) % 20])
+ record(
+ 'group-changed',
+ '{user1} gave group {group} to {user2}',
+ user1=self.users[i],
+ group=self.groups[i],
+ user2=self.users[(i + 1) % 20],
+ )
for i in range(20):
record('logout', '{user} logged out', user=self.users[i])
def test_login(self):
for i, event in zip(range(20), Journal.objects.for_tag('login').order_by('id')):
- self.assertEqual(force_text(event), 'user{0} logged in'.format(i))
+ self.assertEqual(force_text(event), f'user{i} logged in')
def test_groups(self):
for i, event in zip(range(40), Journal.objects.for_tag('group-changed').order_by('id')):
- self.assertEqual(force_text(event),
- 'user{0} gave group group{0} to user{1}'.format(i, (i+1)%20))
+ self.assertEqual(
+ force_text(event), 'user{0} gave group group{0} to user{1}'.format(i, (i + 1) % 20)
+ )
def test_logout(self):
for i, event in zip(range(20), Journal.objects.for_tag('logout').order_by('id')):
- self.assertEqual(force_text(event), 'user{0} logged out'.format(i))
+ self.assertEqual(force_text(event), f'user{i} logged out')
def test_export_as_csv(self):
qs = Journal.objects.all()
l = list(export_as_csv_generator(qs))
- self.assertEquals(set(l[0]), set(['time', 'tag', 'message', 'group', 'group__id', 'user', 'user__id', 'user1', 'user1__id', 'user2', 'user2__id']))
+ self.assertEquals(
+ set(l[0]),
+ {
+ 'time',
+ 'tag',
+ 'message',
+ 'group',
+ 'group__id',
+ 'user',
+ 'user__id',
+ 'user1',
+ 'user1__id',
+ 'user2',
+ 'user2__id',
+ },
+ )
l = list(export_as_csv_generator(qs[:5]))
- self.assertEquals(set(l[0]), set(['time', 'tag', 'message', 'user', 'user__id']))
+ self.assertEquals(set(l[0]), {'time', 'tag', 'message', 'user', 'user__id'})
for user in self.users:
user.delete()
qs = Journal.objects.all()
@@ -58,8 +75,8 @@ class JournalTestCase(TestCase):
def test_error_record(db):
error_record('error', 'error message')
journal = Journal.objects.first()
- assert journal.tag.name == u'error'
- assert journal.message == u'error message'
+ assert journal.tag.name == 'error'
+ assert journal.message == 'error message'
# specifying None as database use the defaut one
error_record('error', 'error message', using=None)