Skip to content

Commit 2e3e325

Browse files
manelclosddabble
andauthored
use new style middleware and catch exceptions to be sure to clear request in context (#1188)
* use new style middleware also, catch exceptions to be sure to clear request in context * Raise exception variable for explicitness * Test HistoricalRecords.context.request is deleted * Updated docs for #1188 Added a changelog entry, and removed the "Using django-webtest with Middleware" section in `common_issues.rst`, as it's not an issue anymore now that `HistoricalRecords.context.request` is deleted in all cases (due to using a `finally` block). * Removed deleting request variable in admin tests This code was added in 341aec1 - when `HistoryRequestMiddleware` didn't delete the `HistoricalRecords.context.request` variable. The middleware class does now, so this can be removed. * Refactored HistoricalRecords.context.request test Now it also tests that the `request` attribute of `HistoricalRecords.context` actually exists while handling each request. --------- Co-authored-by: Anders <[email protected]>
1 parent 60466d6 commit 2e3e325

File tree

7 files changed

+56
-36
lines changed

7 files changed

+56
-36
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Unreleased
1616
- Dropped support for Django 4.0, which reached end-of-life on 2023-04-01 (gh-1202)
1717
- Added support for Django 4.2 (gh-1202)
1818
- Made ``bulk_update_with_history()`` return the number of model rows updated (gh-1206)
19+
- Fixed ``HistoryRequestMiddleware`` not cleaning up after itself (i.e. deleting
20+
``HistoricalRecords.context.request``) under some circumstances (gh-1188)
1921

2022
3.3.0 (2023-03-08)
2123
------------------

docs/common_issues.rst

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -124,29 +124,6 @@ Tracking Custom Users
124124
cannot be set directly on a swapped user model because of the user
125125
foreign key to track the user making changes.
126126

127-
Using django-webtest with Middleware
128-
------------------------------------
129-
130-
When using django-webtest_ to test your Django project with the
131-
django-simple-history middleware, you may run into an error similar to the
132-
following::
133-
134-
django.db.utils.IntegrityError: (1452, 'Cannot add or update a child row: a foreign key constraint fails (`test_env`.`core_historicaladdress`, CONSTRAINT `core_historicaladdress_history_user_id_0f2bed02_fk_user_user_id` FOREIGN KEY (`history_user_id`) REFERENCES `user_user` (`id`))')
135-
136-
.. _django-webtest: https://github.com/django-webtest/django-webtest
137-
138-
This error occurs because ``django-webtest`` sets
139-
``DEBUG_PROPAGATE_EXCEPTIONS`` to true preventing the middleware from cleaning
140-
up the request. To solve this issue, add the following code to any
141-
``clean_environment`` or ``tearDown`` method that
142-
you use:
143-
144-
.. code-block:: python
145-
146-
from simple_history.middleware import HistoricalRecords
147-
if hasattr(HistoricalRecords.context, 'request'):
148-
del HistoricalRecords.context.request
149-
150127
Using F() expressions
151128
---------------------
152129
``F()`` expressions, as described here_, do not work on models that have

simple_history/middleware.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
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 context/thread variable, making it
108
available to the model-level utilities to allow tracking of the authenticated user
119
making a change.
1210
"""
1311

14-
def process_request(self, request):
15-
HistoricalRecords.context.request = request
12+
def __init__(self, get_response):
13+
self.get_response = get_response
1614

17-
def process_response(self, request, response):
18-
if hasattr(HistoricalRecords.context, "request"):
15+
def __call__(self, request):
16+
HistoricalRecords.context.request = request
17+
try:
18+
response = self.get_response(request)
19+
except Exception as e:
20+
raise e
21+
finally:
1922
del HistoricalRecords.context.request
2023
return response

simple_history/tests/tests/test_admin.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,6 @@ class AdminSiteTest(TestCase):
6060
def setUp(self):
6161
self.user = User.objects.create_superuser("user_login", "[email protected]", "pass")
6262

63-
def tearDown(self):
64-
try:
65-
del HistoricalRecords.context.request
66-
except AttributeError:
67-
pass
68-
6963
def login(self, user=None, superuser=None):
7064
user = user or self.user
7165
if superuser is not None:

simple_history/tests/tests/test_middleware.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from datetime import date
2+
from unittest import mock
23

4+
from django.http import HttpResponse
35
from django.test import TestCase, override_settings
46
from django.urls import reverse
57

8+
from simple_history.models import HistoricalRecords
69
from simple_history.tests.custom_user.models import CustomUser
710
from simple_history.tests.models import (
811
BucketDataRegisterRequestUser,
@@ -146,6 +149,38 @@ def test_bucket_member_is_set_on_create_view_when_logged_in(self):
146149

147150
self.assertListEqual([h.history_user_id for h in history], [member1.id])
148151

152+
# The `request` attribute of `HistoricalRecords.context` should be deleted
153+
# even if this setting is set to `True`
154+
@override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True)
155+
@mock.patch("simple_history.tests.view.MockableView.get")
156+
def test_request_attr_is_deleted_after_each_response(self, func_mock):
157+
"""https://github.com/jazzband/django-simple-history/issues/1189"""
158+
159+
def assert_has_request_attr(has_attr: bool):
160+
self.assertEqual(hasattr(HistoricalRecords.context, "request"), has_attr)
161+
162+
def mocked_get(*args, **kwargs):
163+
assert_has_request_attr(True)
164+
response_ = HttpResponse(status=200)
165+
response_.historical_records_request = HistoricalRecords.context.request
166+
return response_
167+
168+
func_mock.side_effect = mocked_get
169+
self.client.force_login(self.user)
170+
mockable_url = reverse("mockable")
171+
172+
assert_has_request_attr(False)
173+
response = self.client.get(mockable_url)
174+
assert_has_request_attr(False)
175+
# Check that the `request` attr existed while handling the request
176+
self.assertEqual(response.historical_records_request.user, self.user)
177+
178+
func_mock.side_effect = RuntimeError()
179+
with self.assertRaises(RuntimeError):
180+
self.client.get(mockable_url)
181+
# The request variable should be deleted even if an exception was raised
182+
assert_has_request_attr(False)
183+
149184

150185
@override_settings(**middleware_override_settings)
151186
class MiddlewareBulkOpsTest(TestCase):

simple_history/tests/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from simple_history.tests.view import (
55
BucketDataRegisterRequestUserCreate,
66
BucketDataRegisterRequestUserDetail,
7+
MockableView,
78
PollBulkCreateView,
89
PollBulkCreateWithDefaultUserView,
910
PollBulkUpdateView,
@@ -55,4 +56,5 @@
5556
PollBulkUpdateWithDefaultUserView.as_view(),
5657
name="poll-bulk-update-with-default-user",
5758
),
59+
path("mockable/", MockableView.as_view(), name="mockable"),
5860
]

simple_history/tests/view.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,10 @@ class BucketDataRegisterRequestUserCreate(CreateView):
109109
class BucketDataRegisterRequestUserDetail(DetailView):
110110
model = BucketDataRegisterRequestUser
111111
fields = ["data"]
112+
113+
114+
class MockableView(View):
115+
"""This view exists to easily mock a response."""
116+
117+
def get(self, request, *args, **kwargs):
118+
return HttpResponse(status=200)

0 commit comments

Comments
 (0)