Skip to content

Commit bd3d3a1

Browse files
author
Ross Mechanic
authored
Merge pull request #364 from treyhunner/fix-middleware-on-django
Fixed HistoryRequestMiddleware so that it works with Django version >= 1.10
2 parents 29ef7ca + 3c62c56 commit bd3d3a1

File tree

7 files changed

+270
-55
lines changed

7 files changed

+270
-55
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Unreleased
55
----------
66
- Dropped support for Django<=1.9 (gh-356)
77
- Fix bug where history_view ignored user permissions (gh-361)
8+
– Fixed HistoryRequestMiddleware which hadn't been working for Django>1.9 (gh-364)
89

910
1.9.1 (2018-03-30)
1011
------------------

simple_history/middleware.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
1-
from django.utils.deprecation import MiddlewareMixin
2-
31
from .models import HistoricalRecords
42

53

6-
class HistoryRequestMiddleware(MiddlewareMixin):
4+
class HistoryRequestMiddleware:
75
"""Expose request to HistoricalRecords.
86
97
This middleware sets request as a local thread variable, making it
108
available to the model-level utilities to allow tracking of the
119
authenticated user making a change.
1210
"""
1311

14-
def process_request(self, request):
12+
def __init__(self, get_response):
13+
self.get_response = get_response
14+
15+
def __call__(self, request):
16+
# Code to be executed for each request before
17+
# the view (and later middleware) are called.
18+
1519
HistoricalRecords.thread.request = request
1620

17-
def process_response(self, request, response):
21+
response = self.get_response(request)
22+
23+
# Code to be executed for each request/response after
24+
# the view is called.
25+
1826
if hasattr(HistoricalRecords.thread, 'request'):
1927
del HistoricalRecords.thread.request
28+
2029
return response

simple_history/tests/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.apps import apps
44
from django.db import models
5+
from django.urls import reverse
56

67
from simple_history import register
78
from simple_history.models import HistoricalRecords
@@ -17,6 +18,9 @@ class Poll(models.Model):
1718

1819
history = HistoricalRecords()
1920

21+
def get_absolute_url(self):
22+
return reverse('poll-detail', kwargs={'pk': self.pk})
23+
2024

2125
class PollWithExcludeFields(models.Model):
2226
question = models.CharField(max_length=200)
@@ -309,6 +313,7 @@ class ContactRegister(models.Model):
309313
name = models.CharField(max_length=30)
310314
email = models.EmailField(max_length=255, unique=True)
311315

316+
312317
register(ContactRegister, table_name='contacts_register_history')
313318

314319

simple_history/tests/tests/test_admin.py

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ def get_history_url(obj, history_index=None, site="admin"):
3535
site=site, app=app, model=model), args=[quote(obj.pk)])
3636

3737

38+
overridden_settings = {
39+
'MIDDLEWARE': settings.MIDDLEWARE +
40+
['simple_history.middleware.HistoryRequestMiddleware'],
41+
}
42+
43+
3844
class AdminSiteTest(WebTest):
3945
def setUp(self):
4046
self.user = User.objects.create_superuser('user_login',
@@ -196,53 +202,37 @@ def test_history_user_not_saved(self):
196202
"No way to know of request, history_user should be unset.",
197203
)
198204

205+
@override_settings(**overridden_settings)
199206
def test_middleware_saves_user(self):
200-
overridden_settings = {
201-
'MIDDLEWARE':
202-
settings.MIDDLEWARE +
203-
['simple_history.middleware.HistoryRequestMiddleware'],
204-
}
205-
with override_settings(**overridden_settings):
206-
self.login()
207-
form = self.app.get(reverse('admin:tests_book_add')).form
208-
form["isbn"] = "9780147_513731"
209-
form.submit()
210-
book = Book.objects.get()
211-
historical_book = book.history.all()[0]
212-
213-
self.assertEqual(historical_book.history_user, self.user,
214-
"Middleware should make the request available to "
215-
"retrieve history_user.")
207+
self.login()
208+
form = self.app.get(reverse('admin:tests_book_add')).form
209+
form["isbn"] = "9780147_513731"
210+
form.submit()
211+
book = Book.objects.get()
212+
historical_book = book.history.all()[0]
213+
214+
self.assertEqual(historical_book.history_user, self.user,
215+
"Middleware should make the request available to "
216+
"retrieve history_user.")
216217

218+
@override_settings(**overridden_settings)
217219
def test_middleware_unsets_request(self):
218-
overridden_settings = {
219-
'MIDDLEWARE':
220-
settings.MIDDLEWARE +
221-
['simple_history.middleware.HistoryRequestMiddleware'],
222-
}
223-
with override_settings(**overridden_settings):
224-
self.login()
225-
self.app.get(reverse('admin:tests_book_add'))
226-
self.assertFalse(hasattr(HistoricalRecords.thread, 'request'))
220+
self.login()
221+
self.app.get(reverse('admin:tests_book_add'))
222+
self.assertFalse(hasattr(HistoricalRecords.thread, 'request'))
227223

224+
@override_settings(**overridden_settings)
228225
def test_rolled_back_user_does_not_lead_to_foreign_key_error(self):
229226
# This test simulates the rollback of a user after a request (which
230227
# happens, e.g. in test cases), and verifies that subsequently
231228
# creating a new entry does not fail with a foreign key error.
229+
self.login()
230+
self.assertEqual(
231+
self.app.get(reverse('admin:tests_book_add')).status_code,
232+
200,
233+
)
232234

233-
overridden_settings = {
234-
'MIDDLEWARE':
235-
settings.MIDDLEWARE +
236-
['simple_history.middleware.HistoryRequestMiddleware'],
237-
}
238-
with override_settings(**overridden_settings):
239-
self.login()
240-
self.assertEqual(
241-
self.app.get(reverse('admin:tests_book_add')).status_code,
242-
200,
243-
)
244-
245-
book = Book.objects.create(isbn="9780147_513731")
235+
book = Book.objects.create(isbn="9780147_513731")
246236

247237
historical_book = book.history.all()[0]
248238

@@ -251,19 +241,14 @@ def test_rolled_back_user_does_not_lead_to_foreign_key_error(self):
251241
"No way to know of request, history_user should be unset.",
252242
)
253243

244+
@override_settings(**overridden_settings)
254245
def test_middleware_anonymous_user(self):
255-
overridden_settings = {
256-
'MIDDLEWARE':
257-
settings.MIDDLEWARE +
258-
['simple_history.middleware.HistoryRequestMiddleware'],
259-
}
260-
with override_settings(**overridden_settings):
261-
self.app.get(reverse('admin:index'))
262-
poll = Poll.objects.create(question="why?", pub_date=today)
263-
historical_poll = poll.history.all()[0]
264-
self.assertEqual(historical_poll.history_user, None,
265-
"Middleware request user should be able to "
266-
"be anonymous.")
246+
self.app.get(reverse('admin:index'))
247+
poll = Poll.objects.create(question="why?", pub_date=today)
248+
historical_poll = poll.history.all()[0]
249+
self.assertEqual(historical_poll.history_user, None,
250+
"Middleware request user should be able to "
251+
"be anonymous.")
267252

268253
def test_other_admin(self):
269254
"""Test non-default admin instances.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
from datetime import date
2+
3+
from django.conf import settings
4+
from django.test import TestCase, override_settings
5+
from django.urls import reverse
6+
7+
from simple_history.tests.custom_user.models import CustomUser
8+
from simple_history.tests.models import Poll
9+
10+
overridden_settings = {
11+
'MIDDLEWARE': settings.MIDDLEWARE +
12+
['simple_history.middleware.HistoryRequestMiddleware'],
13+
}
14+
15+
16+
class MiddlewareTest(TestCase):
17+
def setUp(self):
18+
self.user = CustomUser.objects.create_superuser(
19+
'user_login',
20+
21+
'pass'
22+
)
23+
24+
@override_settings(**overridden_settings)
25+
def test_user_is_set_on_create_view_when_logged_in(self):
26+
self.client.force_login(self.user)
27+
data = {
28+
'question': 'Test question',
29+
'pub_date': '2010-01-01'
30+
}
31+
self.client.post(reverse('poll-add'), data=data)
32+
polls = Poll.objects.all()
33+
self.assertEqual(polls.count(), 1)
34+
35+
poll_history = polls.first().history.all()
36+
37+
self.assertListEqual([ph.history_user_id for ph in poll_history],
38+
[self.user.id])
39+
40+
@override_settings(**overridden_settings)
41+
def test_user_is_not_set_on_create_view_not_logged_in(self):
42+
data = {
43+
'question': 'Test question',
44+
'pub_date': '2010-01-01'
45+
}
46+
self.client.post(reverse('poll-add'), data=data)
47+
polls = Poll.objects.all()
48+
self.assertEqual(polls.count(), 1)
49+
50+
poll_history = polls.first().history.all()
51+
52+
self.assertListEqual([ph.history_user_id for ph in poll_history],
53+
[None])
54+
55+
@override_settings(**overridden_settings)
56+
def test_user_is_set_on_update_view_when_logged_in(self):
57+
self.client.force_login(self.user)
58+
poll = Poll.objects.create(
59+
question='Test question',
60+
pub_date=date.today()
61+
)
62+
data = {
63+
'question': 'Test question updated',
64+
'pub_date': '2010-01-01'
65+
}
66+
self.client.post(reverse('poll-update', args=[poll.pk]), data=data)
67+
68+
polls = Poll.objects.all()
69+
self.assertEqual(polls.count(), 1)
70+
71+
poll = polls.first()
72+
self.assertEqual(poll.question, 'Test question updated')
73+
74+
poll_history = poll.history.all()
75+
76+
self.assertListEqual([ph.history_user_id for ph in poll_history],
77+
[self.user.id, None])
78+
79+
@override_settings(**overridden_settings)
80+
def test_user_is_not_set_on_update_view_when_not_logged_in(self):
81+
poll = Poll.objects.create(
82+
question='Test question',
83+
pub_date=date.today()
84+
)
85+
data = {
86+
'question': 'Test question updated',
87+
'pub_date': '2010-01-01'
88+
}
89+
self.client.post(reverse('poll-update', args=[poll.pk]), data=data)
90+
91+
polls = Poll.objects.all()
92+
self.assertEqual(polls.count(), 1)
93+
94+
poll = polls.first()
95+
self.assertEqual(poll.question, 'Test question updated')
96+
97+
poll_history = poll.history.all()
98+
99+
self.assertListEqual([ph.history_user_id for ph in poll_history],
100+
[None, None])
101+
102+
@override_settings(**overridden_settings)
103+
def test_user_is_unset_on_update_view_after_logging_out(self):
104+
self.client.force_login(self.user)
105+
poll = Poll.objects.create(
106+
question='Test question',
107+
pub_date=date.today()
108+
)
109+
data = {
110+
'question': 'Test question updated',
111+
'pub_date': '2010-01-01'
112+
}
113+
self.client.post(reverse('poll-update', args=[poll.pk]), data=data)
114+
115+
polls = Poll.objects.all()
116+
self.assertEqual(polls.count(), 1)
117+
118+
poll = polls.first()
119+
self.assertEqual(poll.question, 'Test question updated')
120+
121+
self.client.logout()
122+
123+
new_data = {
124+
'question': 'Test question updated part 2',
125+
'pub_date': '2010-01-01'
126+
}
127+
self.client.post(reverse('poll-update', args=[poll.pk]), data=new_data)
128+
129+
polls = Poll.objects.all()
130+
self.assertEqual(polls.count(), 1)
131+
132+
poll = polls.first()
133+
self.assertEqual(poll.question, 'Test question updated part 2')
134+
135+
poll_history = poll.history.all()
136+
137+
self.assertListEqual([ph.history_user_id for ph in poll_history],
138+
[None, self.user.id, None])
139+
140+
@override_settings(**overridden_settings)
141+
def test_user_is_set_on_delete_view_when_logged_in(self):
142+
self.client.force_login(self.user)
143+
poll = Poll.objects.create(
144+
question='Test question',
145+
pub_date=date.today()
146+
)
147+
148+
self.client.post(reverse('poll-delete', args=[poll.pk]))
149+
150+
polls = Poll.objects.all()
151+
self.assertEqual(polls.count(), 0)
152+
153+
poll_history = poll.history.all()
154+
155+
self.assertListEqual([ph.history_user_id for ph in poll_history],
156+
[self.user.id, None])
157+
158+
@override_settings(**overridden_settings)
159+
def test_user_is_not_set_on_delete_view_when_not_logged_in(self):
160+
poll = Poll.objects.create(
161+
question='Test question',
162+
pub_date=date.today()
163+
)
164+
165+
self.client.post(reverse('poll-delete', args=[poll.pk]))
166+
167+
polls = Poll.objects.all()
168+
self.assertEqual(polls.count(), 0)
169+
170+
poll_history = poll.history.all()
171+
172+
self.assertListEqual([ph.history_user_id for ph in poll_history],
173+
[None, None])

simple_history/tests/urls.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
from django.conf.urls import url
44
from django.contrib import admin
55

6+
from simple_history.tests.view import PollCreate, PollUpdate, PollDelete, PollDetail, PollList
67
from . import other_admin
78

89
admin.autodiscover()
910

1011
urlpatterns = [
1112
url(r'^admin/', admin.site.urls),
1213
url(r'^other-admin/', other_admin.site.urls),
14+
url(r'^poll/add/$', PollCreate.as_view(), name='poll-add'),
15+
url(r'^poll/(?P<pk>[0-9]+)/$', PollUpdate.as_view(), name='poll-update'),
16+
url(r'^poll/(?P<pk>[0-9]+)/delete/$', PollDelete.as_view(),
17+
name='poll-delete'),
18+
url(r'^polls/(?P<pk>[0-9]+)/$', PollDetail.as_view(), name='poll-detail'),
19+
url(r'^polls/$', PollList.as_view(), name='poll-list')
1320
]

0 commit comments

Comments
 (0)