From 921a1eadc345a4313e3ad6ce627fd8caeeb918fb Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
Date: Tue, 10 Mar 2020 14:58:19 +0100
Subject: [PATCH] add python3 support (#40614)

---
 debian/control            | 12 +++++++++---
 django_journal/actions.py | 16 ++++++++--------
 django_journal/journal.py |  9 +++++----
 django_journal/models.py  | 15 ++++++++++-----
 tests/test_main.py        |  7 ++++---
 tox.ini                   |  3 +--
 6 files changed, 37 insertions(+), 25 deletions(-)

diff --git a/debian/control b/debian/control
index a9b2553..b04b36f 100644
--- a/debian/control
+++ b/debian/control
@@ -2,10 +2,16 @@ Source: python-django-journal
 Maintainer: Entr'ouvert <info@entrouvert.com>
 Section: python
 Priority: optional
-Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6), debhelper (>= 7.4.3),
- python-django (>= 1.8)
+Build-Depends: python3-setuptools, python3-all, python3-django, python-setuptools (>= 0.6b3), python-all (>= 2.6), debhelper (>= 7.4.3), python-django (>= 1.8)
 Standards-Version: 3.9.1
-X-Python-Version: >= 2.7
+
+Package: python3-django-journal
+Architecture: all
+Depends: ${misc:Depends},  ${python3:Depends},
+    python3-django (>= 1:1.11)
+Description: Django application to keep a structured -- i.e. not just log
+ strings -- journal of events in your project (Python 3 module)
+
 
 Package: python-django-journal
 Architecture: all
diff --git a/django_journal/actions.py b/django_journal/actions.py
index a08852a..e5bdbf5 100644
--- a/django_journal/actions.py
+++ b/django_journal/actions.py
@@ -3,7 +3,7 @@ import csv
 
 from django.utils.translation import ugettext_lazy as _
 from django.http import HttpResponse
-from django.core import exceptions
+from django.utils.encoding import force_text
 
 
 from . import models
@@ -14,24 +14,24 @@ def export_as_csv_generator(queryset):
     for tag in list(tags):
         tags.add('%s__id' % tag)
     tags |= set(models.Tag.objects.filter(stringdata__journal__in=queryset).values_list('name', flat=True))
-    extra_headers = map(lambda s: s.encode('utf-8'), sorted(tags))
+    extra_headers = list(sorted(tags))
     yield header+extra_headers
     for journal in queryset:
         row = {
                 'time': journal.time.isoformat(' '),
-                'tag': journal.tag.name.encode('utf-8'),
-                'message': unicode(journal).encode('utf-8'),
+                '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[row_name] = stringdata.content.encode('utf-8')
+            row[force_text(row_name)] = force_text(stringdata.content)
         for objectdata in journal.objectdata_set.all():
-            row_name = objectdata.tag.name.encode('utf-8')
-            row[row_name+'__id'] = str(objectdata.object_id)
+            row_name = force_text(objectdata.tag.name)
+            row[row_name + '__id'] = str(objectdata.object_id)
             if objectdata.content_object is None:
                 row[row_name] = '<deleted>'
             else:
-                row[row_name] = unicode(objectdata.content_object).encode('utf-8')
+                row[row_name] = force_text(objectdata.content_object)
         yield row
 
 def export_as_csv(modeladmin, request, queryset):
diff --git a/django_journal/journal.py b/django_journal/journal.py
index 7996c2e..69892a8 100644
--- a/django_journal/journal.py
+++ b/django_journal/journal.py
@@ -3,6 +3,7 @@ import logging
 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
 from .exceptions import JournalException
@@ -28,12 +29,12 @@ def record(tag, template, using=None, **kwargs):
        kwargs:
            a mapping of object or data to interpolate in the format string
     '''
-    template = unicode(template)
+    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), e:
+    except (KeyError, IndexError) as e:
         raise JournalException(
                 'Missing variable for the template message', template, e)
     try:
@@ -51,7 +52,7 @@ def record(tag, template, using=None, **kwargs):
             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.iteritems():
+    for name, value in kwargs.items():
         if value is None:
             continue
         tag = Tag.objects.using(using).get_cached(name=name)
@@ -61,7 +62,7 @@ def record(tag, template, using=None, **kwargs):
                 object_id=value.pk
             )
         else:
-            journal.stringdata_set.create(tag=tag, content=unicode(value))
+            journal.stringdata_set.create(tag=tag, content=force_text(value))
     return journal
 
 
diff --git a/django_journal/models.py b/django_journal/models.py
index a39a25b..c51abc2 100644
--- a/django_journal/models.py
+++ b/django_journal/models.py
@@ -2,11 +2,13 @@ import string
 
 from django.db import models
 from django.contrib.contenttypes.fields import GenericForeignKey
+from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
-import managers
+from . import managers
 
 
+@python_2_unicode_compatible
 class Tag(models.Model):
     '''Tag allows typing event and data linked to events.
 
@@ -17,7 +19,7 @@ class Tag(models.Model):
     name = models.CharField(verbose_name=_('name'), max_length=32, unique=True,
             db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.name
 
     def natural_key(self):
@@ -28,6 +30,7 @@ class Tag(models.Model):
         verbose_name = _('tag')
 
 
+@python_2_unicode_compatible
 class Template(models.Model):
     '''Template for formatting an event.
 
@@ -38,7 +41,7 @@ class Template(models.Model):
     content = models.TextField(verbose_name=_('content'), unique=True,
             db_index=True)
 
-    def __unicode__(self):
+    def __str__(self):
         return self.content
 
     def natural_key(self):
@@ -48,6 +51,7 @@ class Template(models.Model):
         ordering = ('content',)
 
 
+@python_2_unicode_compatible
 class Journal(models.Model):
     '''One line of the journal.
 
@@ -98,7 +102,7 @@ class Journal(models.Model):
                 tag=Tag.objects.get_cached(name=tag_name),
                 content_object=obj).save()
 
-    def __unicode__(self):
+    def __str__(self):
         ctx = self.message_context()
         return self.template.content.format(**ctx)
 
@@ -127,6 +131,7 @@ class StringData(models.Model):
         verbose_name = _('linked text string')
 
 
+@python_2_unicode_compatible
 class ObjectData(models.Model):
     '''Object data associated with a recorded event.
 
@@ -150,5 +155,5 @@ class ObjectData(models.Model):
         unique_together = (('journal', 'tag'),)
         verbose_name = _('linked object')
 
-    def __unicode__(self):
+    def __str__(self):
         return u'{0}:{1}:{2}'.format(self.journal.id, self.tag, self.content_object)
diff --git a/tests/test_main.py b/tests/test_main.py
index 6dfa4e4..766b4cd 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,6 +1,7 @@
 from django.test import TestCase
 from django.contrib.auth.models import User, Group
 from django.db import transaction
+from django.utils.encoding import force_text
 
 
 from django_journal.journal import record, error_record
@@ -30,16 +31,16 @@ class JournalTestCase(TestCase):
 
     def test_login(self):
         for i, event in zip(range(20), Journal.objects.for_tag('login').order_by('id')):
-            self.assertEqual(unicode(event), 'user{0} logged in'.format(i))
+            self.assertEqual(force_text(event), 'user{0} logged in'.format(i))
 
     def test_groups(self):
         for i, event in zip(range(40), Journal.objects.for_tag('group-changed').order_by('id')):
-            self.assertEqual(unicode(event),
+            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(unicode(event), 'user{0} logged out'.format(i))
+            self.assertEqual(force_text(event), 'user{0} logged out'.format(i))
 
     def test_export_as_csv(self):
         qs = Journal.objects.all()
diff --git a/tox.ini b/tox.ini
index 365c5da..daf7836 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,9 @@
 [tox]
 toxworkdir = /tmp/tox-{env:USER}/django-journal/{env:BRANCH_NAME:}
-envlist = py2-coverage-{django111,django18}
+envlist = py2-coverage-{django111,django18},py3-django111
 
 
 [testenv]
-basepython = python2
 usedevelop = True
 deps =
   django18: django<1.9
-- 
2.20.1

