Projet

Général

Profil

0001-django-1.11-support.patch

Emmanuel Cazenave, 17 janvier 2018 15:52

Télécharger (19,5 ko)

Voir les différences:

Subject: [PATCH] django 1.11 support

* breaks backward compatibility : now nothing is importable directly from the root module
(because django 1.11 requires almost empty root __init__.py for application)
* create a migration file (django 1.11 start application friendliness)
* adopt EO standard packaging practices (in packaging commands and version management)
* adopt EO standard tests and CI practices (tox, pytest, jenkins.sh)
 django_journal/__init__.py                |  75 ----------------------
 django_journal/admin.py                   |   6 +-
 django_journal/main.py                    |  72 +++++++++++++++++++++
 django_journal/middleware.py              |   8 +--
 django_journal/migrations/0001_initial.py | 103 ++++++++++++++++++++++++++++++
 django_journal/migrations/__init__.py     |   0
 django_journal/models.py                  |   6 +-
 django_journal/tests.py                   |  20 ++++--
 jenkins.sh                                |   4 ++
 setup.py                                  |  64 +++++++++----------
 tox.ini                                   |   9 +++
 11 files changed, 243 insertions(+), 124 deletions(-)
 create mode 100644 django_journal/main.py
 create mode 100644 django_journal/migrations/0001_initial.py
 create mode 100644 django_journal/migrations/__init__.py
 create mode 100755 jenkins.sh
 create mode 100644 tox.ini
django_journal/__init__.py
1
import logging
2

  
3
from exceptions import JournalException
4
from models import (Journal, Tag, Template)
5

  
6
import django.db.models
7
from django.conf import settings
8

  
9
from decorator import atomic
10

  
11
__all__ = ('record', 'error_record', 'Journal')
12
__version__ = '1.25.1'
13

  
14
def unicode_truncate(s, length, encoding='utf-8'):
15
    '''Truncate an unicode string so that its UTF-8 encoding is less than
16
       length.'''
17
    encoded = s.encode(encoding)[:length]
18
    return encoded.decode(encoding, 'ignore')
19

  
20
@atomic
21
def record(tag, template, using=None, **kwargs):
22
    '''Record an event in the journal. The modification is done inside the
23
       current transaction.
24

  
25
       tag:
26
           a string identifier giving the type of the event
27
       tpl:
28
           a format string to describe the event
29
       kwargs:
30
           a mapping of object or data to interpolate in the format string
31
    '''
32
    template = unicode(template)
33
    tag = Tag.objects.using(using).get_cached(name=tag)
34
    template = Template.objects.using(using).get_cached(content=template)
35
    try:
36
        message = template.content.format(**kwargs)
37
    except (KeyError, IndexError), e:
38
        raise JournalException(
39
                'Missing variable for the template message', template, e)
40
    try:
41
        logger = logging.getLogger('django.journal.%s' % tag)
42
        if tag.name == 'error' or tag.name.startswith('error-'):
43
            logger.error(message)
44
        elif tag.name == 'warning' or tag.name.startswith('warning-'):
45
            logger.warning(message)
46
        else:
47
            logger.info(message)
48
    except:
49
        try:
50
            logging.getLogger('django.journal').exception('Unable to log msg')
51
        except:
52
            pass # we tried, really, we tried
53
    journal = Journal.objects.using(using).create(tag=tag, template=template,
54
            message=unicode_truncate(message, 128))
55
    for name, value in kwargs.iteritems():
56
        if value is None:
57
            continue
58
        tag = Tag.objects.using(using).get_cached(name=name)
59
        if isinstance(value, django.db.models.Model):
60
            journal.objectdata_set.create(tag=tag, content_object=value)
61
        else:
62
            journal.stringdata_set.create(tag=tag, content=unicode(value))
63
    return journal
64

  
65
def error_record(tag, tpl, **kwargs):
66
    '''Records error events.
67

  
68
       You must use this function when logging error events. It uses another
69
       database alias than the default one to be immune to transaction rollback
70
       when logging in the middle of a transaction which is going to
71
       rollback.
72
    '''
73
    return record(tag, tpl,
74
            using=getattr(settings, 'JOURNAL_DB_FOR_ERROR_ALIAS', 'default'),
75
            **kwargs)
django_journal/admin.py
4 4
from django.contrib.contenttypes.models import ContentType
5 5
from django.utils.html import escape
6 6

  
7
from models import Journal, Tag, ObjectData, StringData
8

  
9 7
from django.db import models
10 8
from django.utils.translation import ugettext_lazy as _
11 9
from django.utils.html import escape
12 10
from django.core.urlresolvers import reverse, NoReverseMatch
13 11

  
14
import actions
12
from . import actions
13
from .models import Journal, Tag, ObjectData, StringData
14

  
15 15

  
16 16
class ModelAdminFormatter(Formatter):
17 17
    def __init__(self, model_admin=None, filter_link=True,
django_journal/main.py
1
import logging
2

  
3
from django.conf import settings
4
import django.db.models
5

  
6
from .decorator import atomic
7
from .exceptions import JournalException
8
from .models import (Journal, Tag, Template)
9

  
10

  
11
def unicode_truncate(s, length, encoding='utf-8'):
12
    '''Truncate an unicode string so that its UTF-8 encoding is less than
13
       length.'''
14
    encoded = s.encode(encoding)[:length]
15
    return encoded.decode(encoding, 'ignore')
16

  
17
@atomic
18
def record(tag, template, using=None, **kwargs):
19
    '''Record an event in the journal. The modification is done inside the
20
       current transaction.
21

  
22
       tag:
23
           a string identifier giving the type of the event
24
       tpl:
25
           a format string to describe the event
26
       kwargs:
27
           a mapping of object or data to interpolate in the format string
28
    '''
29
    template = unicode(template)
30
    tag = Tag.objects.using(using).get_cached(name=tag)
31
    template = Template.objects.using(using).get_cached(content=template)
32
    try:
33
        message = template.content.format(**kwargs)
34
    except (KeyError, IndexError), e:
35
        raise JournalException(
36
                'Missing variable for the template message', template, e)
37
    try:
38
        logger = logging.getLogger('django.journal.%s' % tag)
39
        if tag.name == 'error' or tag.name.startswith('error-'):
40
            logger.error(message)
41
        elif tag.name == 'warning' or tag.name.startswith('warning-'):
42
            logger.warning(message)
43
        else:
44
            logger.info(message)
45
    except:
46
        try:
47
            logging.getLogger('django.journal').exception('Unable to log msg')
48
        except:
49
            pass # we tried, really, we tried
50
    journal = Journal.objects.using(using).create(tag=tag, template=template,
51
            message=unicode_truncate(message, 128))
52
    for name, value in kwargs.iteritems():
53
        if value is None:
54
            continue
55
        tag = Tag.objects.using(using).get_cached(name=name)
56
        if isinstance(value, django.db.models.Model):
57
            journal.objectdata_set.create(tag=tag, content_object=value)
58
        else:
59
            journal.stringdata_set.create(tag=tag, content=unicode(value))
60
    return journal
61

  
62
def error_record(tag, tpl, **kwargs):
63
    '''Records error events.
64

  
65
       You must use this function when logging error events. It uses another
66
       database alias than the default one to be immune to transaction rollback
67
       when logging in the middle of a transaction which is going to
68
       rollback.
69
    '''
70
    return record(tag, tpl,
71
            using=getattr(settings, 'JOURNAL_DB_FOR_ERROR_ALIAS', 'default'),
72
            **kwargs)
django_journal/middleware.py
1
import django_journal
1
from . import main
2 2

  
3 3
class JournalMiddleware(object):
4 4
    '''Add record and error_record methods to the request object to log
......
15 15
                kwargs['user'] = user
16 16
            if 'ip' not in kwargs:
17 17
                kwargs['ip'] = ip
18
            django_journal.record(tag, template, using=using,**kwargs)
19
        def error_record(tag, template, using=None, **kwargs):
18
            main.record(tag, template, using=using, **kwargs)
19
        def error_record(tag, template, **kwargs):
20 20
            if 'user' not in kwargs:
21 21
                kwargs['user'] = user
22 22
            if 'ip' not in kwargs:
23 23
                kwargs['ip'] = ip
24
            django_journal.error_record(tag, template, using=using, **kwargs)
24
            main.error_record(tag, template, **kwargs)
25 25
        request.record = record
26 26
        request.error_record = error_record
27 27
        return None
django_journal/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.9 on 2018-01-16 08:28
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import django.db.models.deletion
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    initial = True
12

  
13
    dependencies = [
14
        ('contenttypes', '0002_remove_content_type_name'),
15
    ]
16

  
17
    operations = [
18
        migrations.CreateModel(
19
            name='Journal',
20
            fields=[
21
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22
                ('time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='time')),
23
                ('message', models.CharField(db_index=True, max_length=128, verbose_name='message')),
24
            ],
25
            options={
26
                'ordering': ('-id',),
27
                'verbose_name': 'journal entry',
28
                'verbose_name_plural': 'journal entries',
29
            },
30
        ),
31
        migrations.CreateModel(
32
            name='ObjectData',
33
            fields=[
34
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35
                ('object_id', models.PositiveIntegerField(db_index=True, verbose_name='object id')),
36
                ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='content type')),
37
                ('journal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_journal.Journal', verbose_name='journal entry')),
38
            ],
39
            options={
40
                'verbose_name': 'linked object',
41
            },
42
        ),
43
        migrations.CreateModel(
44
            name='StringData',
45
            fields=[
46
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
47
                ('content', models.TextField(verbose_name='content')),
48
                ('journal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_journal.Journal', verbose_name='journal entry')),
49
            ],
50
            options={
51
                'verbose_name': 'linked text string',
52
            },
53
        ),
54
        migrations.CreateModel(
55
            name='Tag',
56
            fields=[
57
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
58
                ('name', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='name')),
59
            ],
60
            options={
61
                'ordering': ('name',),
62
                'verbose_name': 'tag',
63
            },
64
        ),
65
        migrations.CreateModel(
66
            name='Template',
67
            fields=[
68
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
69
                ('content', models.TextField(db_index=True, unique=True, verbose_name='content')),
70
            ],
71
            options={
72
                'ordering': ('content',),
73
            },
74
        ),
75
        migrations.AddField(
76
            model_name='stringdata',
77
            name='tag',
78
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_journal.Tag', verbose_name='tag'),
79
        ),
80
        migrations.AddField(
81
            model_name='objectdata',
82
            name='tag',
83
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_journal.Tag', verbose_name='tag'),
84
        ),
85
        migrations.AddField(
86
            model_name='journal',
87
            name='tag',
88
            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='django_journal.Tag', verbose_name='tag'),
89
        ),
90
        migrations.AddField(
91
            model_name='journal',
92
            name='template',
93
            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='django_journal.Template', verbose_name='template'),
94
        ),
95
        migrations.AlterUniqueTogether(
96
            name='stringdata',
97
            unique_together=set([('journal', 'tag')]),
98
        ),
99
        migrations.AlterUniqueTogether(
100
            name='objectdata',
101
            unique_together=set([('journal', 'tag')]),
102
        ),
103
    ]
django_journal/models.py
1 1
import string
2 2

  
3 3
from django.db import models
4
from django.contrib.contenttypes import generic
4
from django.contrib.contenttypes.fields import GenericForeignKey
5 5
from django.utils.translation import ugettext_lazy as _
6 6

  
7
import managers
7
from . import managers
8 8

  
9 9

  
10 10
class Tag(models.Model):
......
143 143
            verbose_name=_('content type'))
144 144
    object_id = models.PositiveIntegerField(db_index=True,
145 145
            verbose_name=_('object id'))
146
    content_object = generic.GenericForeignKey('content_type',
146
    content_object = GenericForeignKey('content_type',
147 147
            'object_id')
148 148

  
149 149
    class Meta:
django_journal/tests.py
1 1
from django.test import TestCase
2 2
from django.contrib.auth.models import User, Group
3
from django.db import transaction
3
from django.db.transaction import atomic
4 4

  
5

  
6
from . import record
5
from .main import record
7 6
from . import actions
8 7
from . import models
9 8

  
10 9

  
11 10
class JournalTestCase(TestCase):
12 11
    def setUp(self):
13
        models.JOURNAL_METADATA_CACHE_TIMEOUT = 0
14 12
        self.users = []
15 13
        self.groups = []
16
        with transaction.commit_on_success():
14
        with atomic():
17 15
            for i in range(20):
18 16
                self.users.append(
19 17
                        User.objects.create(username='user%s' % i))
......
54 52
        l = list(actions.export_as_csv_generator(qs))
55 53
        self.assertEquals(l[1]['user'], '<deleted>')
56 54

  
55

  
56
def test_middleware(db):
57

  
58
    class FakeRequest(object):
59
        META = dict()
60

  
61
    from .middleware import JournalMiddleware
62
    jm = JournalMiddleware()
63
    request = FakeRequest()
64
    jm.process_request(request)
65
    request.record('yes', 'we can')
66
    request.error_record('me', 'too')
jenkins.sh
1
#!/bin/bash -e
2

  
3
pip install -U tox
4
tox -r
setup.py
1 1
#!/usr/bin/python
2 2
import os
3
import subprocess
3 4
import sys
4 5

  
5 6
from setuptools import setup, find_packages
6 7
from setuptools.command.install_lib import install_lib as _install_lib
7 8
from distutils.command.build import build as _build
8
from setuptools.command.sdist import sdist as _sdist
9
from setuptools.command.sdist import sdist
9 10
from distutils.cmd import Command
10 11

  
11 12

  
......
59 60
    sub_commands = [('compile_translations', None)] + _build.sub_commands
60 61

  
61 62

  
62
class sdist(_sdist):
63
    sub_commands = [('compile_translations', None)] + _sdist.sub_commands
63
class eo_sdist(sdist):
64

  
65
    def run(self):
66
        print "creating VERSION file"
67
        if os.path.exists('VERSION'):
68
            os.remove('VERSION')
69
        version = get_version()
70
        version_file = open('VERSION', 'w')
71
        version_file.write(version)
72
        version_file.close()
73
        sdist.run(self)
74
        print "removing VERSION file"
75
        if os.path.exists('VERSION'):
76
            os.remove('VERSION')
64 77

  
65 78

  
66 79
class install_lib(_install_lib):
......
70 83

  
71 84

  
72 85
def get_version():
73
    import glob
74
    import re
75
    import os
76

  
77
    version = None
78
    for d in glob.glob('*'):
79
        if not os.path.isdir(d):
80
            continue
81
        module_file = os.path.join(d, '__init__.py')
82
        if not os.path.exists(module_file):
83
            continue
84
        for v in re.findall("""__version__ *= *['"](.*)['"]""",
85
                open(module_file).read()):
86
            assert version is None
87
            version = v
88
        if version:
89
            break
90
    assert version is not None
86
    if os.path.exists('VERSION'):
87
        version_file = open('VERSION', 'r')
88
        version = version_file.read()
89
        version_file.close()
90
        return version
91 91
    if os.path.exists('.git'):
92
        import subprocess
93
        p = subprocess.Popen(['git','describe','--dirty', '--match=v*'],
94
                stdout=subprocess.PIPE)
92
        p = subprocess.Popen(['git', 'describe', '--match=v*'], stdout=subprocess.PIPE)
95 93
        result = p.communicate()[0]
96
        assert p.returncode == 0, 'git returned non-zero'
97
        new_version = result.split()[0][1:]
98
        major_minor_release = new_version.split('-')[0]
99
        assert  version == major_minor_release, \
100
                '__version__ (%s) must match the last git annotated tag (%s)' % (version, major_minor_release)
101
        version = new_version.replace('-', '.').replace('.g', '+g')
102
    return version
94
        version = result.split()[0][1:]
95
        version = version.replace('-', '.')
96
        return version
97
    return '0'
98

  
103 99

  
104 100
setup(name='django-journal',
105 101
      version=get_version(),
......
116 112
          'build': build,
117 113
          'install_lib': install_lib,
118 114
          'compile_translations': compile_translations,
119
          'sdist': sdist,
115
          'sdist': eo_sdist,
120 116
          'test': test
121 117
      },
122 118
      install_requires=[
123
          'django >= 1.7',
119
          'django >= 1.11,<2.0',
124 120
          'django-model-utils',
125 121
      ],
126 122
      setup_requires=[
127
          'django >= 1.4.2',
123
          'django >= 1.11,<2.0',
128 124
      ])
tox.ini
1
[testenv]
2
usedevelop = True
3
deps =
4
  pytest
5
  pytest-django
6
setenv =
7
  DJANGO_SETTINGS_MODULE=test_settings
8
commands =
9
  {posargs:py.test --junitxml=junit.xml django_journal/tests.py}
0
-