def url_patterns(self): # First, add our "positively" flagged URLs, where when the flag # matches the defined state, the view is served for the pattern # and not the fallback. url_patterns = [] for pattern in super(FlaggedURLResolver, self).url_patterns: # Get the fallback view, if there is one, and remove it from # the list of fallback patterns. fallback = self.fallback if isinstance(self.fallback, (list, tuple)): fallback = next( (p.callback for p in self.fallback_patterns if p.pattern.describe() == pattern.pattern.describe()), None, ) flag_decorator = flag_check(self.flag_name, self.state, fallback=fallback) route_pattern = pattern.pattern flagged_pattern = URLPattern( route_pattern, flag_decorator(pattern.callback), pattern.default_args, pattern.name, ) url_patterns.append(flagged_pattern) # Next, add "negatively" flagged URLs, where the flag does not match # the defined state, for any remaining fallback patterns that didn't # match other url patterns. described_patterns = [p.pattern.describe() for p in url_patterns] negative_patterns = (p for p in self.fallback_patterns if p.pattern.describe() not in described_patterns) for pattern in negative_patterns: flag_decorator = flag_check(self.flag_name, not self.state) route_pattern = pattern.pattern flagged_pattern = URLPattern( route_pattern, flag_decorator(pattern.callback), pattern.default_args, pattern.name, ) url_patterns.append(flagged_pattern) return url_patterns
def test_decorated_flag_enabled(self): def view(request): return HttpResponse("ok") decorated = flag_check("FLAG_ENABLED", True)(view) response = decorated(self.request) self.assertContains(response, "ok")
def test_pass_if_not_set_no_flag_exists(self): def view(request): return HttpResponse("ok") decorated = flag_check("FLAG_DOES_NOT_EXIST", False)(view) response = decorated(self.request) self.assertContains(response, "ok")
def _flagged_path(flag_name, route, view, kwargs=None, name=None, state=True, fallback=None, Pattern=None): """ Make a URL depend on the state of a feature flag """ if callable(view): flagged_view = flag_check(flag_name, state, fallback=fallback)(view) if Pattern: # pragma: no cover route_pattern = Pattern(route, name=name, is_endpoint=True) else: # pragma: no cover route_pattern = route return URLPattern(route_pattern, flagged_view, kwargs, name) elif isinstance(view, (list, tuple)): urlconf_module, app_name, namespace = view if Pattern: # pragma: no cover route_pattern = Pattern(route, name=name, is_endpoint=True) else: # pragma: no cover route_pattern = route return FlaggedURLResolver( flag_name, route_pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace, state=state, fallback=fallback) else: raise TypeError('view must be a callable')
def test_pass_if_not_set_disabled(self): def view(request): return HttpResponse("ok") decorated = flag_check("FLAG_DISABLED", False)(view) response = decorated(self.request) self.assertContains(response, "ok")
def as_view(cls, **initkwargs): flag_name = initkwargs.get("flag_name", cls.flag_name) state = initkwargs.get("state", cls.state) fallback = initkwargs.get("fallback", cls.fallback) condition = initkwargs.get("condition", cls.condition) if flag_name is None: raise ImproperlyConfigured( "FlaggedViewMixin requires a 'flag_name' argument.") if condition is not None: warnings.warn( "condition attribute of FlaggedViewMixin is deprecated and " "will be removed in a future version of Django-Flags. " "Please use the state attribute instead.", FutureWarning, ) state = condition view = super(FlaggedViewMixin, cls).as_view(**initkwargs) decorator = flag_check( flag_name, state, fallback=fallback, ) return decorator(view)
def test_fallback_view(self): def fallback(request, **kwargs): return HttpResponse("fallback") decorator = flag_check("FLAG_DISABLED", True, fallback=fallback) decorated = decorator(self.view) response = decorated(self.request) self.assertContains(response, "fallback")
def test_fallback_view(self): def fallback(request): return HttpResponse('fallback') decorator = flag_check('FLAG_DISABLED', True, fallback=fallback) decorated = decorator(self.view) response = decorated(self.request) self.assertContains(response, 'fallback')
def test_pass_if_not_set_no_flag_exists(self): def view(request): return HttpResponse('ok') decorated = flag_check(self.flag_name, False)(view) response = decorated(self.request) if isinstance(response.content, str): content = response.content else: content = bytes.decode(response.content) self.assertEqual(content, 'ok')
def test_view_fallback_same_args(self): def view(request, extra_arg, kwarg=None): return HttpResponse('ok') # pragma: no cover def fallback(request, extra_arg, kwarg=None): return HttpResponse('fallback') decorator = flag_check('FLAG_DISABLED', True, fallback=fallback) decorated = decorator(view) response = decorated(self.request, 'an extra argument', kwarg='foo') self.assertContains(response, 'fallback')
def test_view_fallback_same_args(self): def view(request, extra_arg, kwarg=None): return HttpResponse("ok") # pragma: no cover def fallback(request, extra_arg, kwarg=None): return HttpResponse("fallback") decorator = flag_check("FLAG_DISABLED", True, fallback=fallback) decorated = decorator(view) response = decorated(self.request, "an extra argument", kwarg="foo") self.assertContains(response, "fallback")
def test_fallback_view(self): def fallback(request): return HttpResponse('fallback') decorator = flag_check(self.flag_name, True, fallback=fallback) decorated = decorator(self.view) response = decorated(self.request) if isinstance(response.content, str): content = response.content else: content = bytes.decode(response.content) self.assertEqual(content, 'fallback')
def test_pass_if_not_set_disabled(self): def view(request): return HttpResponse('ok') Flag.objects.create(key=self.flag_name, enabled_by_default=False) decorated = flag_check(self.flag_name, False)(view) response = decorated(self.request) if isinstance(response.content, str): content = response.content else: content = bytes.decode(response.content) self.assertEqual(content, 'ok')
def test_view_fallback_different_args(self): def view(request, extra_arg, kwarg=None): return HttpResponse("ok") # pragma: no cover def fallback(request): return HttpResponse("fallback") # pragma: no cover decorator = flag_check("FLAG_DISABLED", True, fallback=fallback) with warnings.catch_warnings(record=True) as warning_list: decorator(view) self.assertTrue( any(item.category == RuntimeWarning for item in warning_list))
def test_pass_if_not_set_fallback_view(self): def fallback(request, **kwargs): return HttpResponse("fallback") decorator = flag_check( "FLAG_ENABLED", False, fallback=fallback, ) decorated = decorator(self.view) response = decorated(self.request) self.assertContains(response, "fallback")
def test_pass_if_not_set_fallback_view(self): def fallback(request): return HttpResponse('fallback') decorator = flag_check( 'FLAG_ENABLED', False, fallback=fallback, ) decorated = decorator(self.view) response = decorated(self.request) self.assertContains(response, 'fallback')
def dispatch(self, request, *args, **kwargs): if self.flag_name is None: raise ImproperlyConfigured( "FlaggedViewMixin requires a 'flag_name' argument.") super_dispatch = super(FlaggedViewMixin, self).dispatch decorator = flag_check( self.flag_name, self.condition, fallback=self.fallback, ) return decorator(super_dispatch)(request, *args, **kwargs)
def test_pass_if_not_set_fallback_view(self): Flag.objects.create(key=self.flag_name, enabled_by_default=True) def fallback(request): return HttpResponse('fallback') decorator = flag_check( self.flag_name, False, fallback=fallback, ) decorated = decorator(self.view) response = decorated(self.request) if isinstance(response.content, str): content = response.content else: content = bytes.decode(response.content) self.assertEqual(content, 'fallback')
def flagged_url(flag_name, regex, view, kwargs=None, name=None, condition=True, fallback=None): """ Make a URL depend on the state of a feature flag """ if callable(view): flagged_view = flag_check(flag_name, condition, fallback=fallback)(view) return url(regex, flagged_view, kwargs=kwargs, name=name) elif isinstance(view, (list, tuple)): # XXX: For right now, we don't support include() raise TypeError('Flagged include() is not supported') else: raise TypeError('view must be a callable')
def test_pass_if_not_set_enabled(self): decorated = flag_check("FLAG_ENABLED", False)(self.view) self.assertRaises(Http404, decorated, self.request) self.assertEqual(self.mock_view.call_count, 0)
def test_decorated_no_flag_exists(self): decorated = flag_check(self.flag_name, True)(self.view) self.assertRaises(Http404, decorated, self.request) self.assertEqual(self.view.call_count, 0)
def test_decorated_flag_disabled(self): decorated = flag_check("FLAG_DISABLED", True)(self.view) self.assertRaises(Http404, decorated, self.request) self.assertEqual(self.mock_view.call_count, 0)
def test_decorated_no_flag_exists(self): decorated = flag_check("FLAG_DOES_NOT_EXIST", True)(self.view) with self.assertRaises(Http404): decorated(self.request) self.assertEqual(self.mock_view.call_count, 0)
def test_pass_if_not_set_enabled(self): Flag.objects.create(key=self.flag_name, enabled_by_default=True) decorated = flag_check(self.flag_name, False)(self.view) self.assertRaises(Http404, decorated, self.request) self.assertEqual(self.view.call_count, 0)