Skip to content

Commit

Permalink
Add Class-Based View Mixins (#340)
Browse files Browse the repository at this point in the history
  • Loading branch information
peteretep authored and clintonb committed Jul 1, 2019
1 parent e4a8c62 commit aca4ccc
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/usage/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ JavaScript.

.. toctree::
:titlesonly:

views
decorators
mixins
templates
javascript
cli
42 changes: 42 additions & 0 deletions docs/usage/mixins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.. _usage-mixins:

============================
Mixins for Class Based Views
============================

Waffle provides mixins to add to Class Based Views.

When the flag or switch is active, or a sample returns True, the view executes normally.
When it is inactive, the view returns a 404.

WaffleFlagMixin
===============

.. code-block:: python
from waffle.mixins import WaffleFlagMixin
class MyClass(WaffleFlagMixin, View):
waffle_flag = "my_flag"
WaffleSwitchMixin
=================

.. code-block:: python
from waffle.mixins import WaffleSwitchMixin
class MyClass(WaffleSwitchMixin, View):
waffle_switch= "my_switch"
WaffleSampleMixin
=================

.. code-block:: python
from waffle.mixins import WaffleSampleMixin
class MyClass(WaffleSampleMixin, View):
waffle_switch= "my_sample"
31 changes: 31 additions & 0 deletions test_app/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from django.http import HttpResponse
from django.shortcuts import render
from django.template.loader import render_to_string
from django.views.generic import View

from waffle import flag_is_active
from waffle.decorators import waffle_flag, waffle_switch
from waffle.mixins import WaffleFlagMixin, WaffleSampleMixin, WaffleSwitchMixin


def flag_in_view(request):
Expand Down Expand Up @@ -105,3 +107,32 @@ def flagged_view_with_args_with_valid_url_name(request, some_number):
@waffle_flag('foo', redirect_to='invalid_view')
def flagged_view_with_invalid_redirect(request):
return HttpResponse('foo')


class BaseWaffleView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('foo')


class FlagView(WaffleFlagMixin, BaseWaffleView):
waffle_flag = 'foo'


class FlagOffView(WaffleFlagMixin, BaseWaffleView):
waffle_flag = '!foo'


class SampleView(WaffleSampleMixin, BaseWaffleView):
waffle_sample = 'foo'


class SampleOffView(WaffleSampleMixin, BaseWaffleView):
waffle_sample = '!foo'


class SwitchView(WaffleSwitchMixin, BaseWaffleView):
waffle_switch = 'foo'


class SwitchOffView(WaffleSwitchMixin, BaseWaffleView):
waffle_switch = '!foo'
70 changes: 70 additions & 0 deletions waffle/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from functools import partial

from django.http import Http404

from waffle import switch_is_active, flag_is_active, sample_is_active


class BaseWaffleMixin(object):

def validate_waffle(self, waffle, func):
if waffle.startswith('!'):
active = not func(waffle[1:])
else:
active = func(waffle)
return active

def invalid_waffle(self):
raise Http404('Inactive waffle')


class WaffleFlagMixin(BaseWaffleMixin):
"""
Checks that as flag is active, or 404. Operates like the FBV decorator
waffle_flag
"""

waffle_flag = None

def dispatch(self, request, *args, **kwargs):
func = partial(flag_is_active, request)
active = self.validate_waffle(self.waffle_flag, func)

if not active:
return self.invalid_waffle()

return super(WaffleFlagMixin, self).dispatch(request, *args, **kwargs)


class WaffleSampleMixin(BaseWaffleMixin):
"""
Checks that as switch is active, or 404. Operates like the FBV decorator
waffle_sample.
"""

waffle_sample = None

def dispatch(self, request, *args, **kwargs):
active = self.validate_waffle(self.waffle_sample, sample_is_active)

if not active:
return self.invalid_waffle()

return super(WaffleSampleMixin, self).dispatch(request, *args, **kwargs)


class WaffleSwitchMixin(BaseWaffleMixin):
"""
Checks that as switch is active, or 404. Operates like the FBV decorator
waffle_switch.
"""

waffle_switch = None

def dispatch(self, request, *args, **kwargs):
active = self.validate_waffle(self.waffle_switch, switch_is_active)

if not active:
return self.invalid_waffle()

return super(WaffleSwitchMixin, self).dispatch(request, *args, **kwargs)
102 changes: 102 additions & 0 deletions waffle/tests/test_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from django.contrib.auth.models import AnonymousUser
from django.http import Http404
from django.test import RequestFactory

from test_app import views
from waffle.middleware import WaffleMiddleware
from waffle.models import Flag, Sample, Switch
from waffle.tests.base import TestCase


def get(**kw):
request = RequestFactory().get('/foo', data=kw)
request.user = AnonymousUser()
return request


def process_request(request, view):
response = view.as_view()(request)
return WaffleMiddleware().process_response(request, response)


class WaffleFlagMixinTest(TestCase):
def setUp(self):
self.request = get()

def test_flag_must_be_active(self):
view = views.FlagView
self.assertRaises(Http404, process_request, self.request, view)

Flag.objects.create(name='foo', everyone=True)
response = process_request(self.request, view)
self.assertEqual(b'foo', response.content)

def test_flag_must_be_inactive(self):
view = views.FlagOffView
response = process_request(self.request, view)
self.assertEqual(b'foo', response.content)

Flag.objects.create(name='foo', everyone=True)
self.assertRaises(Http404, process_request, self.request, view)

def test_override_with_cookie(self):
Flag.objects.create(name='foo', percent='0.1')
self.request.COOKIES['dwf_foo'] = 'True'
response = process_request(self.request, views.FlagView)
self.assertEqual(b'foo', response.content)
self.assertIn('dwf_foo', response.cookies)
self.assertEqual('True', response.cookies['dwf_foo'].value)


class WaffleSampleMixinTest(TestCase):
def setUp(self):
self.request = get()

def test_sample_must_be_active(self):
view = views.SampleView
self.assertRaises(Http404, process_request, self.request, view)

Sample.objects.create(name='foo', percent='100.0')
response = process_request(self.request, view)
self.assertEqual(b'foo', response.content)

def test_sample_must_be_inactive(self):
view = views.SampleOffView
response = process_request(self.request, view)
self.assertEqual(b'foo', response.content)

Sample.objects.create(name='foo', percent='100.0')
self.assertRaises(Http404, process_request, self.request, view)

def test_override_with_cookie(self):
Sample.objects.create(name='foo', percent='0.0')
self.request.COOKIES['dwf_foo'] = 'True'
self.assertRaises(Http404, process_request, self.request,
views.SwitchView)


class WaffleSwitchMixinTest(TestCase):
def setUp(self):
self.request = get()

def test_switch_must_be_active(self):
view = views.SwitchView
self.assertRaises(Http404, process_request, self.request, view)

Switch.objects.create(name='foo', active=True)
response = process_request(self.request, view)
self.assertEqual(b'foo', response.content)

def test_switch_must_be_inactive(self):
view = views.SwitchOffView
response = process_request(self.request, view)
self.assertEqual(b'foo', response.content)

Switch.objects.create(name='foo', active=True)
self.assertRaises(Http404, process_request, self.request, view)

def test_no_override_with_cookie(self):
Switch.objects.create(name='foo', active=False)
self.request.COOKIES['dwf_foo'] = 'True'
self.assertRaises(Http404, process_request, self.request,
views.SwitchView)

0 comments on commit aca4ccc

Please sign in to comment.