Merge ~woutervb/django-adminaudit:django2 into django-adminaudit:master

Proposed by Wouter van Bommel
Status: Merged
Approved by: Wouter van Bommel
Approved revision: a1162a77f1e2a852e00dfd7b64fd6f21d563fb05
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~woutervb/django-adminaudit:django2
Merge into: django-adminaudit:master
Diff against target: 913 lines (+171/-158)
16 files modified
.gitignore (+2/-0)
adminaudit/__init__.py (+3/-1)
adminaudit/admin.py (+5/-5)
adminaudit/management/commands/adminaudit_email_report.py (+1/-2)
adminaudit/management/commands/adminaudit_report.py (+1/-4)
adminaudit/models.py (+4/-6)
adminaudit/templates/admin/adminaudit/auditlog/change_form.html (+52/-36)
adminaudit/tests.py (+27/-26)
demo/settings.py (+31/-23)
demo/urls.py (+21/-10)
demo/wsgi.py (+1/-1)
dev/null (+0/-0)
example_app/migrations/0001_initial.py (+2/-2)
example_app/models.py (+7/-3)
setup.py (+11/-4)
tox.ini (+3/-35)
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Review via email: mp+391913@code.launchpad.net

Commit message

Prepare for Django 2.2 framework

Update versioning and testing so that it can be used with the django 2.2
Framework.
Update the tests to include all supported python versions, but allow
some of them be missing.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks good, see minor nitpick in comment inline. Thanks! Approving so you can land after addressing that.

review: Approve
Revision history for this message
Wouter van Bommel (woutervb) wrote :

Updated the code before making the merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index ecfc0d8..8b71146 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -1,5 +1,6 @@
6 db.sqlite3
7 build/
8+dist/
9 django_adminaudit.egg-info/
10 doc/_build/*
11 .coverage
12@@ -7,3 +8,4 @@ coverage
13 MANIFEST
14 .tox
15 attachements
16+__pycache__/
17diff --git a/adminaudit/__init__.py b/adminaudit/__init__.py
18index 8af0d98..85b40e3 100644
19--- a/adminaudit/__init__.py
20+++ b/adminaudit/__init__.py
21@@ -7,7 +7,9 @@ def audit_install():
22 from django.contrib.admin import site
23 from .models import AdminAuditMixin, AuditLog
24
25- for model, model_admin in site._registry.items():
26+ # Directly iterating over site._registry.items() won't work, as we are
27+ # changing values on the go, which cannot be done in python 3+
28+ for model, model_admin in list(site._registry.items()):
29 if (model is AuditLog or isinstance(model_admin, AdminAuditMixin)):
30 # Do not mingle with our own model
31 continue
32diff --git a/adminaudit/admin.py b/adminaudit/admin.py
33index a550ef7..bdb3d48 100644
34--- a/adminaudit/admin.py
35+++ b/adminaudit/admin.py
36@@ -17,8 +17,11 @@ class AuditLogAdmin(admin.ModelAdmin):
37 def has_add_permission(self, request, obj=None):
38 return False
39
40- def delete_view(self, request, object_id, extra_context=None):
41- raise Http404
42+ def has_change_permission(self, request, obj=None):
43+ return False
44+
45+ def has_delete_permission(self, request, obj=None):
46+ return False
47
48 def change_view(self, request, object_id, extra_context=None):
49 if request.method == 'POST':
50@@ -46,8 +49,5 @@ class AuditLogAdmin(admin.ModelAdmin):
51 return super(AuditLogAdmin, self).change_view(
52 request, object_id, extra_context=extra_context)
53
54- def get_actions(self, request):
55- return []
56-
57
58 admin.site.register(AuditLog, AuditLogAdmin)
59diff --git a/adminaudit/management/commands/adminaudit_email_report.py b/adminaudit/management/commands/adminaudit_email_report.py
60index db62187..32c3133 100644
61--- a/adminaudit/management/commands/adminaudit_email_report.py
62+++ b/adminaudit/management/commands/adminaudit_email_report.py
63@@ -4,7 +4,6 @@
64 from operator import itemgetter
65
66 from django.conf import settings
67-from django.template import Context
68 from django.template.loader import get_template
69 from django.core.mail import send_mail
70
71@@ -38,6 +37,6 @@ class Command(AdminAuditBaseCommand):
72 for recipient in recipients:
73 context['recipient'] = recipient
74 self.stdout.write("Sending e-email to: %s\n" % recipient)
75- send_mail(subject, template.render(Context(context)),
76+ send_mail(subject, template.render(context),
77 from_email, [recipient], fail_silently=False)
78 self.stdout.write("done\n")
79diff --git a/adminaudit/management/commands/adminaudit_report.py b/adminaudit/management/commands/adminaudit_report.py
80index 5a216d6..bb71895 100644
81--- a/adminaudit/management/commands/adminaudit_report.py
82+++ b/adminaudit/management/commands/adminaudit_report.py
83@@ -1,7 +1,6 @@
84 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
85 # the GNU Lesser General Public License version 3 (see the file LICENSE).
86
87-from django.template import Context
88 from django.template.loader import get_template
89
90 from adminaudit.management.commands import AdminAuditBaseCommand
91@@ -13,6 +12,4 @@ class Command(AdminAuditBaseCommand):
92
93 def handle(self, *args, **kwargs):
94 template = get_template("adminaudit/report.txt")
95- context = Context(self.context_data())
96-
97- self.stdout.write(template.render(context) + '\n')
98+ self.stdout.write(template.render(self.context_data()) + '\n')
99diff --git a/adminaudit/migrations/0002_auto__chg_field_auditlog_username__chg_field_auditlog_representation__.py b/adminaudit/migrations/0002_auto__chg_field_auditlog_username__chg_field_auditlog_representation__.py
100deleted file mode 100644
101index 158b225..0000000
102--- a/adminaudit/migrations/0002_auto__chg_field_auditlog_username__chg_field_auditlog_representation__.py
103+++ /dev/null
104@@ -1,46 +0,0 @@
105-# -*- coding: utf-8 -*-
106-import datetime
107-from south.db import db
108-from south.v2 import SchemaMigration
109-from django.db import models
110-
111-
112-class Migration(SchemaMigration):
113-
114- def forwards(self, orm):
115-
116- # Changing field 'AuditLog.username'
117- db.alter_column(u'adminaudit_auditlog', 'username', self.gf('django.db.models.fields.TextField')())
118-
119- # Changing field 'AuditLog.representation'
120- db.alter_column(u'adminaudit_auditlog', 'representation', self.gf('django.db.models.fields.TextField')())
121-
122- # Changing field 'AuditLog.model'
123- db.alter_column(u'adminaudit_auditlog', 'model', self.gf('django.db.models.fields.TextField')())
124-
125- def backwards(self, orm):
126-
127- # Changing field 'AuditLog.username'
128- db.alter_column(u'adminaudit_auditlog', 'username', self.gf('django.db.models.fields.CharField')(max_length=255))
129-
130- # Changing field 'AuditLog.representation'
131- db.alter_column(u'adminaudit_auditlog', 'representation', self.gf('django.db.models.fields.CharField')(max_length=255))
132-
133- # Changing field 'AuditLog.model'
134- db.alter_column(u'adminaudit_auditlog', 'model', self.gf('django.db.models.fields.CharField')(max_length=255))
135-
136- models = {
137- u'adminaudit.auditlog': {
138- 'Meta': {'object_name': 'AuditLog'},
139- 'change': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
140- 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
141- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
142- 'model': ('django.db.models.fields.TextField', [], {}),
143- 'representation': ('django.db.models.fields.TextField', [], {}),
144- 'user_id': ('django.db.models.fields.IntegerField', [], {}),
145- 'username': ('django.db.models.fields.TextField', [], {}),
146- 'values': ('django.db.models.fields.TextField', [], {})
147- }
148- }
149-
150- complete_apps = ['adminaudit']
151\ No newline at end of file
152diff --git a/adminaudit/models.py b/adminaudit/models.py
153index a96bcbe..bd5792c 100644
154--- a/adminaudit/models.py
155+++ b/adminaudit/models.py
156@@ -1,14 +1,12 @@
157 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
158 # the GNU Lesser General Public License version 3 (see the file LICENSE).
159
160+import base64
161 import json
162
163 import django
164
165-if django.VERSION < (1, 7):
166- from django.contrib.admin.util import NestedObjects
167-else:
168- from django.contrib.admin.utils import NestedObjects
169+from django.contrib.admin.utils import NestedObjects
170 from django.core import serializers
171 from django.db import models, router
172 from django.db.models.fields.files import FileField
173@@ -46,7 +44,7 @@ class AuditLog(models.Model):
174 field_name = file_field.name
175 fileobj = getattr(obj, field_name)
176 if fileobj.name:
177- content = fileobj.read().encode('base64')
178+ content = base64.b64encode(fileobj.read()).decode('utf-8')
179 json_content['files'][fileobj.name] = content
180 values_pretty = json.dumps(json_content, indent=2, sort_keys=True)
181 return cls.objects.create(
182@@ -54,7 +52,7 @@ class AuditLog(models.Model):
183 user_id=user.id,
184 model=str(obj._meta),
185 values=values_pretty,
186- representation=unicode(obj),
187+ representation=str(obj),
188 change=change,
189 )
190
191diff --git a/adminaudit/templates/admin/adminaudit/auditlog/change_form.html b/adminaudit/templates/admin/adminaudit/auditlog/change_form.html
192index 5b6eab0..49c164e 100644
193--- a/adminaudit/templates/admin/adminaudit/auditlog/change_form.html
194+++ b/adminaudit/templates/admin/adminaudit/auditlog/change_form.html
195@@ -3,38 +3,54 @@
196 {# Copyright 2010-2012 Canonical Ltd. This software is licensed under #}
197 {# the GNU Lesser General Public License version 3 (see the file LICENSE). #}
198
199-{% load i18n %}
200-
201-{% block title %}View audit log| {% trans 'Django site admin' %}{% endblock %}
202-{% block content_title %}<h1>View audit log</h1>{% endblock %}
203-
204-{% block content %}
205-<table>
206- <tbody>
207- <tr>
208- <th>User</th>
209- <td>{{ original.user }}</td>
210- </tr>
211- <tr>
212- <th>Model</th>
213- <td>{{ original.model }}</td>
214- </tr>
215- <tr>
216- <th>Representation:</th>
217- <td>{{ original.representation }}</td>
218- </tr>
219- <tr>
220- <th>Change</th>
221- <td>{{ original.change }}</td>
222- </tr>
223- <tr>
224- <th>Change date</th>
225- <td>{{ original.created_at }}</td>
226- </tr>
227- <tr>
228- <th>Change values</th>
229- <td>
230- <table>
231+{% block field_sets %}
232+<fieldset class="module aligned ">
233+ <div class="form-row field-username">
234+ <div>
235+ <label>Username:</label>
236+ <div class="readonly">{{ original.username }}</div>
237+ </div>
238+ </div>
239+
240+ <div class="form-row field-user_id">
241+ <div>
242+ <label>User ID:</label>
243+ <div class="readonly">{{ original.user_id }}</div>
244+ </div>
245+ </div>
246+
247+ <div class="form-row field-model">
248+ <div>
249+ <label>Model:</label>
250+ <div class="readonly">{{ original.model }}</div>
251+ </div>
252+ </div>
253+
254+ <div class="form-row field-representation">
255+ <div>
256+ <label>Representation:</label>
257+ <div class="readonly">{{ original.representation }}</div>
258+ </div>
259+ </div>
260+
261+ <div class="form-row field-change">
262+ <div>
263+ <label>Change:</label>
264+ <div class="readonly">{{ original.change }}</div>
265+ </div>
266+ </div>
267+
268+ <div class="form-row field-created_at">
269+ <div>
270+ <label>Change date:</label>
271+ <div class="readonly">{{ original.created_at }}</div>
272+ </div>
273+ </div>
274+
275+ <div class="form-row field-values">
276+ <div>
277+ <label>Change values:</label>
278+ <table class="readonly">
279 <tbody>
280 <tr>
281 <th>New values</th>
282@@ -46,8 +62,8 @@
283 </tr>
284 </tbody>
285 </table>
286- </td>
287- </tr>
288- </tbody>
289-</table>
290+ </div>
291+ </div>
292+
293+</fieldset>
294 {% endblock %}
295diff --git a/adminaudit/templates/admin/base_site.html b/adminaudit/templates/admin/base_site.html
296deleted file mode 100644
297index fa7bdd1..0000000
298--- a/adminaudit/templates/admin/base_site.html
299+++ /dev/null
300@@ -1,30 +0,0 @@
301-{% extends "admin/base.html" %}
302-
303-{# Copyright 2010-2012 Canonical Ltd. This software is licensed under #}
304-{# the GNU Lesser General Public License version 3 (see the file LICENSE). #}
305-
306-{% load i18n %}
307-
308-{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
309-
310-{% block footer %}
311-{{ block.super }}
312-<script type="text/javascript">
313- if (document.getElementsByClassName) {
314- var changelinks = document.getElementsByClassName("changelink");
315- for (var i = 0; i < changelinks.length; i += 1) {
316- var a = changelinks[i];
317- if (a.getAttribute("href") == "adminaudit/auditlog/") {
318- a.parentNode.removeChild(a);
319- break;
320- }
321- }
322- }
323-</script>
324-{% endblock %}
325-
326-{% block branding %}
327-<h1 id="site-name">{% trans 'Django administration' %}</h1>
328-{% endblock %}
329-
330-{% block nav-global %}{% endblock %}
331diff --git a/adminaudit/tests.py b/adminaudit/tests.py
332index 9b26ddd..8191388 100644
333--- a/adminaudit/tests.py
334+++ b/adminaudit/tests.py
335@@ -1,30 +1,32 @@
336 # Copyright 2010-2012 Canonical Ltd. This software is licensed under
337 # the GNU Lesser General Public License version 3 (see the file LICENSE).
338
339+import base64
340 import json
341 import os
342
343-from cStringIO import StringIO
344+from io import StringIO
345 from random import choice
346 from string import ascii_letters
347 from datetime import timedelta
348+from unittest.mock import Mock, patch
349
350 import django
351
352 from django.test import TestCase
353 from django.db.models import Model
354 from django.conf import settings
355+from django.contrib.admin.sites import AdminSite
356 from django.core import mail
357 from django.core.files import File
358 from django.core.management import call_command
359-from django.core.urlresolvers import reverse
360 from django.contrib.admin import ModelAdmin, site
361 from django.contrib.auth.models import User, Permission
362+from django.urls import reverse
363 from django.utils.timezone import now
364
365-from mock import Mock, patch
366-
367 from adminaudit import audit_install
368+from adminaudit.admin import AuditLogAdmin
369 from adminaudit.models import AuditLog
370 from adminaudit.management.commands.adminaudit_email_report import (
371 Command as EmailCommand)
372@@ -36,9 +38,6 @@ class BaseTestCase(TestCase):
373
374 def setUp(self):
375 super(BaseTestCase, self).setUp()
376- User.objects.filter(username='test').delete()
377- AuditLog.objects.all().delete()
378-
379 self.user = User.objects.create_superuser(
380 username='test', password='12345678', email='a@a.com')
381
382@@ -58,6 +57,7 @@ class BaseTestCase(TestCase):
383
384
385 class AuditLogTestCase(TestCase):
386+
387 def make_log_data(self, **overrides):
388 data = {
389 'username': 'test',
390@@ -123,7 +123,7 @@ class AuditAdminEmailReportTestCase(TestCase):
391 user = self.create_and_log_in_user(is_superuser=True)
392
393 auditlog_id = AuditLog.create(user, user, 'create').id
394- url = '/admin/adminaudit/auditlog/{0}/'.format(auditlog_id)
395+ url = '/admin/adminaudit/auditlog/{0}/change/'.format(auditlog_id)
396
397 r = self.client.post(url, {
398 'values': 'test',
399@@ -146,14 +146,13 @@ class AuditAdminEmailReportTestCase(TestCase):
400 r = self.client.post(url, {'post': 'yes'})
401
402 self.assertEqual(AuditLog.objects.filter(pk=auditlog_id).count(), 1)
403- self.assertEqual(404, r.status_code)
404-
405- def test_js_for_removing_change_link_from_index_page_is_present(self):
406- self.create_and_log_in_user(is_superuser=True)
407-
408- r = self.client.get(reverse('admin:index'))
409+ self.assertEqual(403, r.status_code)
410
411- self.assertContains(r, 'a.parentNode.removeChild(a)')
412+ def test_no_add_no_delete_no_change_permissions(self):
413+ auditlog_admin = AuditLogAdmin(AuditLog, AdminSite())
414+ self.assertFalse(auditlog_admin.has_add_permission(object()))
415+ self.assertFalse(auditlog_admin.has_change_permission(object()))
416+ self.assertFalse(auditlog_admin.has_delete_permission(object()))
417
418
419 class AdminIntegrationTestCase(BaseTestCase):
420@@ -176,7 +175,7 @@ class AdminIntegrationTestCase(BaseTestCase):
421 self.assertOneLogCreated('create')
422
423 def test_changing_object_via_admin_is_saved_by_audit(self):
424- r = self.client.get('/admin/auth/user/{0}/'.format(self.user.pk))
425+ r = self.client.get('/admin/auth/user/{0}/change/'.format(self.user.pk))
426 data = r.context['adminform'].form.initial
427 data.update({
428 'first_name': "First",
429@@ -186,7 +185,7 @@ class AdminIntegrationTestCase(BaseTestCase):
430 'date_joined_1': data['date_joined'].strftime("%H:%M:%S"),
431 })
432 r = self.client.post(
433- '/admin/auth/user/{0}/'.format(self.user.pk), data)
434+ '/admin/auth/user/{0}/change/'.format(self.user.pk), data)
435 self.assertEqual(302, r.status_code)
436 self.assertOneLogCreated('update')
437
438@@ -230,7 +229,7 @@ class AuditLogAdminChangeViewTestCase(BaseTestCase):
439 auditlog = AuditLog.create(self.user, self.user, 'create')
440
441 r = self.client.get(
442- '/admin/adminaudit/auditlog/{0}/'.format(auditlog.pk))
443+ '/admin/adminaudit/auditlog/{0}/change/'.format(auditlog.pk))
444
445 self.assertContains(r, 'test')
446 self.assertContains(r, 'auth.user')
447@@ -242,7 +241,7 @@ class AuditLogAdminChangeViewTestCase(BaseTestCase):
448 auditlog = AuditLog.create(self.user, self.user, 'update', self.user)
449
450 r = self.client.get(
451- '/admin/adminaudit/auditlog/{0}/'.format(auditlog.pk))
452+ '/admin/adminaudit/auditlog/{0}/change/'.format(auditlog.pk))
453
454 self.assertTrue(r.context['new'])
455 self.assertTrue(r.context['old'])
456@@ -251,7 +250,7 @@ class AuditLogAdminChangeViewTestCase(BaseTestCase):
457 auditlog = AuditLog.create(self.user, self.user, 'delete')
458
459 r = self.client.get(
460- '/admin/adminaudit/auditlog/{0}/'.format(auditlog.pk))
461+ '/admin/adminaudit/auditlog/{0}/change/'.format(auditlog.pk))
462
463 self.assertFalse(r.context['new'])
464 self.assertTrue(r.context['old'])
465@@ -345,7 +344,7 @@ class AdminAuditCLIReportTestCase(TestCase):
466 class FilesTestCase(BaseTestCase):
467
468 def create_post_with_attachement(self):
469- with open('adminaudit/test_data/file_1.dat') as f:
470+ with open('adminaudit/test_data/file_1.dat', 'rb') as f:
471 p = Post.objects.create(author=self.user, title='t', body='b')
472 p.attachement.save('file_1.dat', File(f))
473 p.save()
474@@ -355,9 +354,9 @@ class FilesTestCase(BaseTestCase):
475 def test_changing_file_in_the_admin_leaves_alone_old_file(self):
476 p = self.create_post_with_attachement()
477
478- with open('adminaudit/test_data/file_2.dat') as f:
479+ with open('adminaudit/test_data/file_2.dat', 'rb') as f:
480 response = self.client.post(
481- '/admin/example_app/post/{0}/'.format(p.pk),
482+ '/admin/example_app/post/{0}/change/'.format(p.pk),
483 data={
484 'author': self.user.pk,
485 'title': "Post title",
486@@ -392,8 +391,10 @@ class FilesTestCase(BaseTestCase):
487 self.assertEqual('delete', latest_log.change)
488
489 data = json.loads(latest_log.values)
490- content = data['files'][attachement_path].decode('base64')
491- original_content = open('adminaudit/test_data/file_1.dat').read()
492+ content = base64.b64decode(data['files'][attachement_path])
493+ # be sure to open (and close) the file in binary mode
494+ with open('adminaudit/test_data/file_1.dat', 'rb') as f:
495+ original_content = f.read()
496 self.assertEqual(original_content, content)
497
498
499@@ -415,4 +416,4 @@ class AuditInstallTestCase(TestCase):
500 audit_install()
501 audit_install()
502
503- mock_unregister.assert_called_once(model)
504+ mock_unregister.assert_called_once_with(model)
505diff --git a/demo/settings.py b/demo/settings.py
506index d97a212..042123c 100644
507--- a/demo/settings.py
508+++ b/demo/settings.py
509@@ -1,22 +1,24 @@
510 """
511 Django settings for demo project.
512
513+Generated by 'django-admin startproject' using Django 2.2.16.
514+
515 For more information on this file, see
516-https://docs.djangoproject.com/en/1.7/topics/settings/
517+https://docs.djangoproject.com/en/2.2/topics/settings/
518
519 For the full list of settings and their values, see
520-https://docs.djangoproject.com/en/1.7/ref/settings/
521+https://docs.djangoproject.com/en/2.2/ref/settings/
522 """
523
524-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
525 import os
526 import django
527
528-BASE_DIR = os.path.dirname(os.path.dirname(__file__))
529+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
530+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
531
532
533 # Quick-start development settings - unsuitable for production
534-# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
535+# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
536
537 # SECURITY WARNING: keep the secret key used in production secret!
538 SECRET_KEY = 'ljq4=m7@(1#pkuyq8rza*^-526*etfuh(in8_d(g*5n=mwbod)'
539@@ -24,46 +26,56 @@ SECRET_KEY = 'ljq4=m7@(1#pkuyq8rza*^-526*etfuh(in8_d(g*5n=mwbod)'
540 # SECURITY WARNING: don't run with debug turned on in production!
541 DEBUG = True
542
543-TEMPLATE_DEBUG = True
544-
545 ALLOWED_HOSTS = []
546
547
548 # Application definition
549
550 INSTALLED_APPS = (
551+ 'django.contrib.admin',
552 'django.contrib.auth',
553 'django.contrib.contenttypes',
554 'django.contrib.sessions',
555 'django.contrib.messages',
556 'django.contrib.staticfiles',
557 'adminaudit',
558- 'django.contrib.admin',
559 'example_app',
560 )
561
562-MIDDLEWARE_CLASSES = [
563+MIDDLEWARE = [
564+ 'django.middleware.security.SecurityMiddleware',
565 'django.contrib.sessions.middleware.SessionMiddleware',
566 'django.middleware.common.CommonMiddleware',
567 'django.middleware.csrf.CsrfViewMiddleware',
568 'django.contrib.auth.middleware.AuthenticationMiddleware',
569- 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
570 'django.contrib.messages.middleware.MessageMiddleware',
571 'django.middleware.clickjacking.XFrameOptionsMiddleware',
572 ]
573
574-if django.VERSION < (1, 7):
575- INSTALLED_APPS += ('south',)
576- MIDDLEWARE_CLASSES.remove(
577- 'django.contrib.auth.middleware.SessionAuthenticationMiddleware')
578-
579 ROOT_URLCONF = 'demo.urls'
580
581+TEMPLATES = [
582+ {
583+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
584+ 'DIRS': [],
585+ 'APP_DIRS': True,
586+ 'OPTIONS': {
587+ 'debug': True,
588+ 'context_processors': [
589+ 'django.template.context_processors.debug',
590+ 'django.template.context_processors.request',
591+ 'django.contrib.auth.context_processors.auth',
592+ 'django.contrib.messages.context_processors.messages',
593+ ],
594+ },
595+ },
596+]
597+
598 WSGI_APPLICATION = 'demo.wsgi.application'
599
600
601 # Database
602-# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
603+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
604
605 DATABASES = {
606 'default': {
607@@ -72,8 +84,9 @@ DATABASES = {
608 }
609 }
610
611+
612 # Internationalization
613-# https://docs.djangoproject.com/en/1.7/topics/i18n/
614+# https://docs.djangoproject.com/en/2.2/topics/i18n/
615
616 LANGUAGE_CODE = 'en-us'
617
618@@ -85,11 +98,6 @@ USE_TZ = True
619
620
621 # Static files (CSS, JavaScript, Images)
622-# https://docs.djangoproject.com/en/1.7/howto/static-files/
623+# https://docs.djangoproject.com/en/2.2/howto/static-files/
624
625 STATIC_URL = '/static/'
626-
627-TEMPLATE_DIRS = (
628- os.path.join(os.path.dirname(django.__file__),
629- 'contrib/admin/templates/admin'),
630-)
631diff --git a/demo/urls.py b/demo/urls.py
632index fd96184..f1f71be 100644
633--- a/demo/urls.py
634+++ b/demo/urls.py
635@@ -1,19 +1,30 @@
636-# Copyright 2010-2012 Canonical Ltd. This software is licensed under
637+# Copyright 2010-2020 Canonical Ltd. This software is licensed under
638 # the GNU Lesser General Public License version 3 (see the file LICENSE).
639
640-from django.conf.urls import patterns, include, url
641+"""demo URL Configuration
642+
643+The `urlpatterns` list routes URLs to views. For more information please see:
644+ https://docs.djangoproject.com/en/2.2/topics/http/urls/
645+Examples:
646+Function views
647+ 1. Add an import: from my_app import views
648+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
649+Class-based views
650+ 1. Add an import: from other_app.views import Home
651+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
652+Including another URLconf
653+ 1. Import the include() function: from django.urls import include, path
654+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
655+"""
656+
657 from django.contrib import admin
658+from django.urls import path
659
660 from adminaudit import audit_install
661
662 admin.autodiscover()
663 audit_install()
664
665-urlpatterns = patterns(
666- '',
667- # Examples:
668- # url(r'^$', 'demo.views.home', name='home'),
669- # url(r'^blog/', include('blog.urls')),
670-
671- url(r'^admin/', include(admin.site.urls)),
672-)
673+urlpatterns = [
674+ path('admin/', admin.site.urls),
675+]
676diff --git a/demo/wsgi.py b/demo/wsgi.py
677index ef48754..4e0598d 100644
678--- a/demo/wsgi.py
679+++ b/demo/wsgi.py
680@@ -4,7 +4,7 @@ WSGI config for demo project.
681 It exposes the WSGI callable as a module-level variable named ``application``.
682
683 For more information on this file, see
684-https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
685+https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
686 """
687
688 import os
689diff --git a/example_app/migrations/0001_initial.py b/example_app/migrations/0001_initial.py
690index 0ef2ea7..96104c0 100644
691--- a/example_app/migrations/0001_initial.py
692+++ b/example_app/migrations/0001_initial.py
693@@ -21,8 +21,8 @@ class Migration(migrations.Migration):
694 ('attachement', models.FileField(upload_to=b'attachements')),
695 ('created_at', models.DateTimeField(auto_now_add=True)),
696 ('updated_at', models.DateTimeField(auto_now=True)),
697- ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
698- ('related', models.ForeignKey(blank=True, to='example_app.Post', null=True)),
699+ ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
700+ ('related', models.ForeignKey(blank=True, to='example_app.Post', null=True, on_delete=models.CASCADE)),
701 ],
702 ),
703 ]
704diff --git a/example_app/models.py b/example_app/models.py
705index e5babc8..a9501fe 100644
706--- a/example_app/models.py
707+++ b/example_app/models.py
708@@ -2,14 +2,18 @@
709 # the GNU Lesser General Public License version 3 (see the file LICENSE).
710
711 from django.db import models
712-from django.contrib.auth.models import User
713+from django.contrib.auth import get_user_model
714+
715+
716+User = get_user_model()
717
718
719 class Post(models.Model):
720- author = models.ForeignKey(User)
721+ author = models.ForeignKey(User, on_delete=models.CASCADE)
722 title = models.CharField(max_length=128)
723 body = models.TextField()
724 attachement = models.FileField(upload_to='attachements')
725 created_at = models.DateTimeField(auto_now_add=True)
726 updated_at = models.DateTimeField(auto_now=True)
727- related = models.ForeignKey('self', blank=True, null=True)
728+ related = models.ForeignKey(
729+ 'self', blank=True, null=True, on_delete=models.CASCADE)
730diff --git a/example_app/south_migrations/0001_initial.py b/example_app/south_migrations/0001_initial.py
731deleted file mode 100644
732index ec3f4d7..0000000
733--- a/example_app/south_migrations/0001_initial.py
734+++ /dev/null
735@@ -1,80 +0,0 @@
736-# -*- coding: utf-8 -*-
737-from south.utils import datetime_utils as datetime
738-from south.db import db
739-from south.v2 import SchemaMigration
740-from django.db import models
741-
742-
743-class Migration(SchemaMigration):
744-
745- def forwards(self, orm):
746- # Adding model 'Post'
747- db.create_table('example_app_post', (
748- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
749- ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
750- ('title', self.gf('django.db.models.fields.CharField')(max_length=128)),
751- ('body', self.gf('django.db.models.fields.TextField')()),
752- ('attachement', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
753- ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
754- ('updated_at', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
755- ('related', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['example_app.Post'], null=True, blank=True)),
756- ))
757- db.send_create_signal('example_app', ['Post'])
758-
759-
760- def backwards(self, orm):
761- # Deleting model 'Post'
762- db.delete_table('example_app_post')
763-
764-
765- models = {
766- 'auth.group': {
767- 'Meta': {'object_name': 'Group'},
768- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
769- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
770- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
771- },
772- 'auth.permission': {
773- 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
774- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
775- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
776- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
777- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
778- },
779- 'auth.user': {
780- 'Meta': {'object_name': 'User'},
781- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
782- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
783- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
784- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
785- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
786- 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
787- 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
788- 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
789- 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
790- 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
791- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
792- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
793- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
794- },
795- 'contenttypes.contenttype': {
796- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
797- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
798- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
799- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
800- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
801- },
802- 'example_app.post': {
803- 'Meta': {'object_name': 'Post'},
804- 'attachement': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
805- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
806- 'body': ('django.db.models.fields.TextField', [], {}),
807- 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
808- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
809- 'related': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['example_app.Post']", 'null': 'True', 'blank': 'True'}),
810- 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
811- 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
812- }
813- }
814-
815- complete_apps = ['example_app']
816\ No newline at end of file
817diff --git a/example_app/south_migrations/__init__.py b/example_app/south_migrations/__init__.py
818deleted file mode 100644
819index e69de29..0000000
820--- a/example_app/south_migrations/__init__.py
821+++ /dev/null
822diff --git a/setup.py b/setup.py
823index 2c8cbc0..c2f11b9 100644
824--- a/setup.py
825+++ b/setup.py
826@@ -8,7 +8,7 @@ from setuptools import setup
827 setup(
828 # metadata
829 name='django-adminaudit',
830- version='0.5',
831+ version='2.0.0',
832 description="Extends Django's admin logging capabilities",
833 url='https://launchpad.net/django-adminaudit',
834 author='Ubuntu One Hackers',
835@@ -17,7 +17,11 @@ setup(
836 classifiers=[
837 'Framework :: Django',
838 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
839- 'Programming Language :: Python :: 2.7',
840+ 'Programming Language :: Python :: 3.5',
841+ 'Programming Language :: Python :: 3.6',
842+ 'Programming Language :: Python :: 3.7',
843+ 'Programming Language :: Python :: 3.8',
844+ 'Programming Language :: Python :: 3.9',
845 'Topic :: Software Development :: Libraries',
846 'Topic :: Software Development :: Libraries :: Python Modules',
847 ],
848@@ -28,13 +32,16 @@ setup(
849 'adminaudit.management',
850 'adminaudit.management.commands',
851 'adminaudit.migrations',
852- 'adminaudit.south_migrations',
853 ],
854 package_data={
855 'adminaudit': [
856 'templates/admin/adminaudit/auditlog/*.html',
857- 'templates/admin/*.html',
858 'templates/adminaudit/*.txt',
859 ],
860 },
861+
862+ install_requires=[
863+ 'Django>=2.2,<3',
864+ ],
865+
866 )
867diff --git a/tox.ini b/tox.ini
868index 7fdc8d9..cfce968 100644
869--- a/tox.ini
870+++ b/tox.ini
871@@ -1,39 +1,7 @@
872 [tox]
873 envlist =
874- py27-django14, py27-django15, py27-django16, py27-django17, py27-django18
875+ py35, py36, py37, py38, py39
876+skip_missing_interpreters = true
877
878 [testenv]
879-commands = python manage.py test adminaudit
880-deps =
881- mock==1.0.1
882-
883-[testenv:py27]
884-basepython = python2.7
885-
886-[testenv:py27-django14]
887-deps =
888- {[testenv]deps}
889- django>=1.4,<1.5
890- south==1.0
891-
892-[testenv:py27-django15]
893-deps =
894- {[testenv]deps}
895- django>=1.5,<1.6
896- south==1.0
897-
898-[testenv:py27-django16]
899-deps =
900- {[testenv]deps}
901- django>=1.6,<1.7
902- south==1.0
903-
904-[testenv:py27-django17]
905-deps =
906- {[testenv]deps}
907- django>=1.7,<1.8
908-
909-[testenv:py27-django18]
910-deps =
911- {[testenv]deps}
912- django>=1.8,<1.9.0a
913+commands = python3 manage.py test adminaudit

Subscribers

People subscribed via source and target branches