Merge ~woutervb/django-adminaudit:django2 into django-adminaudit:master
- Git
- lp:~woutervb/django-adminaudit
- django2
- Merge into 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) |
||||
Related bugs: |
|
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.
Description of the change
To post a comment you must log in.
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
1 | diff --git a/.gitignore b/.gitignore |
2 | index 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__/ |
17 | diff --git a/adminaudit/__init__.py b/adminaudit/__init__.py |
18 | index 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 |
32 | diff --git a/adminaudit/admin.py b/adminaudit/admin.py |
33 | index 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) |
59 | diff --git a/adminaudit/management/commands/adminaudit_email_report.py b/adminaudit/management/commands/adminaudit_email_report.py |
60 | index 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") |
79 | diff --git a/adminaudit/management/commands/adminaudit_report.py b/adminaudit/management/commands/adminaudit_report.py |
80 | index 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') |
99 | diff --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 |
100 | deleted file mode 100644 |
101 | index 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 |
152 | diff --git a/adminaudit/models.py b/adminaudit/models.py |
153 | index 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 | |
191 | diff --git a/adminaudit/templates/admin/adminaudit/auditlog/change_form.html b/adminaudit/templates/admin/adminaudit/auditlog/change_form.html |
192 | index 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 %} |
295 | diff --git a/adminaudit/templates/admin/base_site.html b/adminaudit/templates/admin/base_site.html |
296 | deleted file mode 100644 |
297 | index 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 %} |
331 | diff --git a/adminaudit/tests.py b/adminaudit/tests.py |
332 | index 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) |
505 | diff --git a/demo/settings.py b/demo/settings.py |
506 | index 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 | -) |
631 | diff --git a/demo/urls.py b/demo/urls.py |
632 | index 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 | +] |
676 | diff --git a/demo/wsgi.py b/demo/wsgi.py |
677 | index 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 |
689 | diff --git a/example_app/migrations/0001_initial.py b/example_app/migrations/0001_initial.py |
690 | index 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 | ] |
704 | diff --git a/example_app/models.py b/example_app/models.py |
705 | index 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) |
730 | diff --git a/example_app/south_migrations/0001_initial.py b/example_app/south_migrations/0001_initial.py |
731 | deleted file mode 100644 |
732 | index 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 |
817 | diff --git a/example_app/south_migrations/__init__.py b/example_app/south_migrations/__init__.py |
818 | deleted file mode 100644 |
819 | index e69de29..0000000 |
820 | --- a/example_app/south_migrations/__init__.py |
821 | +++ /dev/null |
822 | diff --git a/setup.py b/setup.py |
823 | index 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 | ) |
867 | diff --git a/tox.ini b/tox.ini |
868 | index 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 |
Looks good, see minor nitpick in comment inline. Thanks! Approving so you can land after addressing that.