diff --git a/docs/usage/index.rst b/docs/usage/index.rst index 513ea008..d91d8edb 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -17,4 +17,5 @@ JavaScript. mixins templates javascript + json cli diff --git a/docs/usage/json.rst b/docs/usage/json.rst new file mode 100644 index 00000000..90c2ea96 --- /dev/null +++ b/docs/usage/json.rst @@ -0,0 +1,62 @@ +.. _usage-json: + +===================== +Waffle Status as JSON +===================== + +Although :doc:`WaffleJS` returns the status of all +:ref:`flags `, :ref:`switches `, and +:ref:`samples `, it does so by exposing a Javascript +object, rather than returning the data in a directly consumable format. + +In cases where a directly consumable format is preferrable, +Waffle also exposes this data as JSON via the ``waffle_status`` view. + + +Using the view +-------------- + +Using the ``waffle_status`` view requires adding Waffle to your URL +configuration. For example, in your ``ROOT_URLCONF``:: + + urlpatterns = patterns('', + (r'^', include('waffle.urls')), + ) + +This adds a route called ``waffle_status``, which will return the current +status of each flag, switch, and sample as JSON, with the following structure: + +.. code-block:: json + + { + "flags": { + "flag_active": { + "is_active": true, + "last_modified": "2020-01-01T12:00:00.000" + }, + "flag_inactive": { + "is_active": false, + "last_modified": "2020-01-01T12:00:00.000" + } + }, + "switches": { + "switch_active": { + "is_active": true, + "last_modified": "2020-01-01T12:00:00.000" + }, + "switch_inactive": { + "is_active": false, + "last_modified": "2020-01-01T12:00:00.000" + } + }, + "samples": { + "sample_active": { + "is_active": true, + "last_modified": "2020-01-01T12:00:00.000" + }, + "sample_inactive": { + "is_active": false, + "last_modified": "2020-01-01T12:00:00.000" + } + } + } diff --git a/waffle/__init__.py b/waffle/__init__.py index 26da9c51..0d009db9 100755 --- a/waffle/__init__.py +++ b/waffle/__init__.py @@ -12,17 +12,17 @@ def flag_is_active(request, flag_name, read_only=False): - flag = get_waffle_model('FLAG_MODEL').get(flag_name) + flag = get_waffle_flag_model().get(flag_name) return flag.is_active(request, read_only=read_only) def switch_is_active(switch_name): - switch = get_waffle_model('SWITCH_MODEL').get(switch_name) + switch = get_waffle_switch_model().get(switch_name) return switch.is_active() def sample_is_active(sample_name): - sample = get_waffle_model('SAMPLE_MODEL').get(sample_name) + sample = get_waffle_sample_model().get(sample_name) return sample.is_active() @@ -30,6 +30,14 @@ def get_waffle_flag_model(): return get_waffle_model('FLAG_MODEL') +def get_waffle_switch_model(): + return get_waffle_model('SWITCH_MODEL') + + +def get_waffle_sample_model(): + return get_waffle_model('SAMPLE_MODEL') + + def get_waffle_model(setting_name): """ Returns the waffle Flag model that is active in this project. diff --git a/waffle/tests/test_views.py b/waffle/tests/test_views.py index de95ebb8..f6d4e04c 100644 --- a/waffle/tests/test_views.py +++ b/waffle/tests/test_views.py @@ -1,6 +1,6 @@ from django.urls import reverse -from waffle import get_waffle_flag_model +from waffle import get_waffle_flag_model, get_waffle_sample_model, get_waffle_switch_model from waffle.models import Sample, Switch from waffle.tests.base import TestCase @@ -14,6 +14,41 @@ def test_wafflejs(self): for control in response['cache-control'].split(',')] self.assertIn('max-age=0', cache_control) + def test_waffle_status(self): + response = self.client.get(reverse('waffle_status')) + self.assertEqual(200, response.status_code) + self.assertEqual('application/json', response['content-type']) + cache_control = [control.strip() + for control in response['cache-control'].split(',')] + self.assertIn('max-age=0', cache_control) + + def test_waffle_status_response(self): + get_waffle_flag_model().objects.create(name='test_flag_active', everyone=True) + get_waffle_flag_model().objects.create(name='test_flag_inactive', everyone=False) + get_waffle_switch_model().objects.create(name='test_switch_active', active=True) + get_waffle_switch_model().objects.create(name='test_switch_inactive', active=False) + get_waffle_sample_model().objects.create(name='test_sample_active', percent=100) + get_waffle_sample_model().objects.create(name='test_sample_inactive', percent=0) + + response = self.client.get(reverse('waffle_status')) + self.assertEqual(200, response.status_code) + content = response.json() + + assert 'test_flag_active' in content['flags'].keys() + assert content['flags']['test_flag_active']['is_active'] + assert 'test_flag_inactive' in content['flags'].keys() + assert not content['flags']['test_flag_inactive']['is_active'] + + assert 'test_switch_active' in content['switches'].keys() + assert content['switches']['test_switch_active']['is_active'] + assert 'test_switch_inactive' in content['switches'].keys() + assert not content['switches']['test_switch_inactive']['is_active'] + + assert 'test_sample_active' in content['samples'].keys() + assert content['samples']['test_sample_active']['is_active'] + assert 'test_sample_inactive' in content['samples'].keys() + assert not content['samples']['test_sample_inactive']['is_active'] + def test_flush_all_flags(self): """Test the 'FLAGS_ALL' list gets invalidated correctly.""" get_waffle_flag_model().objects.create(name='myflag1', everyone=True) diff --git a/waffle/urls.py b/waffle/urls.py index 7e2a5502..15b8ee36 100644 --- a/waffle/urls.py +++ b/waffle/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from waffle.views import wafflejs +from waffle.views import wafflejs, waffle_json urlpatterns = [ path('wafflejs', wafflejs, name='wafflejs'), + path('waffle_status', waffle_json, name='waffle_status'), ] diff --git a/waffle/views.py b/waffle/views.py index 8f92f404..3e7397ec 100644 --- a/waffle/views.py +++ b/waffle/views.py @@ -1,9 +1,8 @@ -from django.http import HttpResponse +from django.http import HttpResponse, JsonResponse from django.template import loader from django.views.decorators.cache import never_cache -from waffle import get_waffle_flag_model -from waffle.models import Sample, Switch +from waffle import get_waffle_flag_model, get_waffle_switch_model, get_waffle_sample_model from waffle.utils import get_setting @@ -17,10 +16,10 @@ def _generate_waffle_js(request): flags = get_waffle_flag_model().get_all() flag_values = [(f.name, f.is_active(request)) for f in flags] - switches = Switch.get_all() + switches = get_waffle_switch_model().get_all() switch_values = [(s.name, s.is_active()) for s in switches] - samples = Sample.get_all() + samples = get_waffle_sample_model().get_all() sample_values = [(s.name, s.is_active()) for s in samples] return loader.render_to_string('waffle/waffle.js', { @@ -31,3 +30,43 @@ def _generate_waffle_js(request): 'switch_default': get_setting('SWITCH_DEFAULT'), 'sample_default': get_setting('SAMPLE_DEFAULT'), }) + + +@never_cache +def waffle_json(request): + return JsonResponse(_generate_waffle_json(request)) + + +def _generate_waffle_json(request): + flags = get_waffle_flag_model().get_all() + flag_values = { + f.name: { + 'is_active': f.is_active(request), + 'last_modified': f.modified, + } + for f in flags + } + + switches = get_waffle_switch_model().get_all() + switch_values = { + s.name: { + 'is_active': s.is_active(), + 'last_modified': s.modified, + } + for s in switches + } + + samples = get_waffle_sample_model().get_all() + sample_values = { + s.name: { + 'is_active': s.is_active(), + 'last_modified': s.modified, + } + for s in samples + } + + return { + 'flags': flag_values, + 'switches': switch_values, + 'samples': sample_values, + }