Repository: pytest-dev/pytest-django

Issue:
Improve the settings fixture
Django has a decorator that is called `override_settings` and `modify_settings` (located in https://github.com/django/django/blob/master/django/test/utils.py). Apart from just changing the value in `django.conf.settings`, `override_settings` will also send a signal for changed settings.

Our settings fixture should make use of this these methods internally to make settings changes in a more reliable way.
#46 is related to this.



Discussion:
@pelme anything happening here? Did you have any problems with the implementation in the commit? 

We just stumbled on this problem. We're also happy takeover any PR and fix


Resolving patch:
diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py
--- a/pytest_django/fixtures.py
+++ b/pytest_django/fixtures.py
@@ -269,30 +269,45 @@ def rf():
     return RequestFactory()
 
 
-class MonkeyPatchWrapper(object):
-    def __init__(self, monkeypatch, wrapped_object):
-        super(MonkeyPatchWrapper, self).__setattr__('monkeypatch', monkeypatch)
-        super(MonkeyPatchWrapper, self).__setattr__('wrapped_object',
-                                                    wrapped_object)
+class SettingsWrapper(object):
+    _to_restore = []
 
-    def __getattr__(self, attr):
-        return getattr(self.wrapped_object, attr)
+    def __delattr__(self, attr):
+        from django.test import override_settings
+        override = override_settings()
+        override.enable()
+        from django.conf import settings
+        delattr(settings, attr)
+
+        self._to_restore.append(override)
 
     def __setattr__(self, attr, value):
-        self.monkeypatch.setattr(self.wrapped_object, attr, value,
-                                 raising=False)
+        from django.test import override_settings
+        override = override_settings(**{
+            attr: value
+        })
+        override.enable()
+        self._to_restore.append(override)
 
-    def __delattr__(self, attr):
-        self.monkeypatch.delattr(self.wrapped_object, attr)
+    def __getattr__(self, item):
+        from django.conf import settings
+        return getattr(settings, item)
 
+    def finalize(self):
+        for override in reversed(self._to_restore):
+            override.disable()
 
-@pytest.fixture()
-def settings(monkeypatch):
+        del self._to_restore[:]
+
+
+@pytest.yield_fixture()
+def settings():
     """A Django settings object which restores changes after the testrun"""
     skip_if_no_django()
 
-    from django.conf import settings as django_settings
-    return MonkeyPatchWrapper(monkeypatch, django_settings)
+    wrapper = SettingsWrapper()
+    yield wrapper
+    wrapper.finalize()
 
 
 @pytest.fixture(scope='session')


Test patch:
diff --git a/tests/DBNAME_pytest_django_db b/tests/DBNAME_pytest_django_db
new file mode 100644
diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py
--- a/tests/test_fixtures.py
+++ b/tests/test_fixtures.py
@@ -83,6 +83,77 @@ def test_deleted_again(self, settings):
         assert hasattr(settings, 'SECRET_KEY')
         assert hasattr(real_settings, 'SECRET_KEY')
 
+    def test_signals(self, settings):
+        result = []
+
+        def assert_signal(signal, sender, setting, value, enter):
+            result.append((setting, value, enter))
+
+        from django.test.signals import setting_changed
+        setting_changed.connect(assert_signal)
+
+        result = []
+        settings.SECRET_KEY = 'change 1'
+        settings.SECRET_KEY = 'change 2'
+        assert result == [
+            ('SECRET_KEY', 'change 1', True),
+            ('SECRET_KEY', 'change 2', True),
+        ]
+
+        result = []
+        settings.FOOBAR = 'abc123'
+        assert sorted(result) == [
+            ('FOOBAR', 'abc123', True),
+        ]
+
+    def test_modification_signal(self, django_testdir):
+        django_testdir.create_test_module("""
+            import pytest
+
+            from django.conf import settings
+            from django.test.signals import setting_changed
+
+
+            @pytest.fixture(autouse=True, scope='session')
+            def settings_change_printer():
+                def receiver(sender, **kwargs):
+                    fmt_dict = {'actual_value': getattr(settings, kwargs['setting'],
+                                                        '<<does not exist>>')}
+                    fmt_dict.update(kwargs)
+
+                    print('Setting changed: '
+                          'enter=%(enter)s,setting=%(setting)s,'
+                          'value=%(value)s,actual_value=%(actual_value)s'
+                          % fmt_dict)
+
+                setting_changed.connect(receiver, weak=False)
+
+
+            def test_set(settings):
+                settings.SECRET_KEY = 'change 1'
+                settings.SECRET_KEY = 'change 2'
+
+
+            def test_set_non_existent(settings):
+                settings.FOOBAR = 'abc123'
+         """)
+
+        result = django_testdir.runpytest_subprocess('--tb=short', '-v', '-s')
+
+        # test_set
+        result.stdout.fnmatch_lines([
+            '*Setting changed: enter=True,setting=SECRET_KEY,value=change 1*',
+            '*Setting changed: enter=True,setting=SECRET_KEY,value=change 2*',
+            '*Setting changed: enter=False,setting=SECRET_KEY,value=change 1*',
+            '*Setting changed: enter=False,setting=SECRET_KEY,value=foobar*',
+        ])
+
+        result.stdout.fnmatch_lines([
+            '*Setting changed: enter=True,setting=FOOBAR,value=abc123*',
+            ('*Setting changed: enter=False,setting=FOOBAR,value=None,'
+             'actual_value=<<does not exist>>*'),
+        ])
+
 
 class TestLiveServer:
     def test_url(self, live_server):