Skip to content

Commit

Permalink
Merge pull request #27 from cfpb/wagtail20
Browse files Browse the repository at this point in the history
Wagtail 2.0
  • Loading branch information
willbarton committed Apr 25, 2018
2 parents 9af322d + 426adbd commit d588d68
Show file tree
Hide file tree
Showing 18 changed files with 316 additions and 124 deletions.
10 changes: 2 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,24 @@ matrix:
include:
- env: TOXENV=lint
python: 3.6
- env: TOXENV=py27-dj18-wag110
python: 2.7
- env: TOXENV=py27-dj18-wag112
python: 2.7
- env: TOXENV=py27-dj18-wag113
python: 2.7
- env: TOXENV=py27-dj111-wag110
python: 2.7
- env: TOXENV=py27-dj111-wag112
python: 2.7
- env: TOXENV=py27-dj111-wag113
python: 2.7
- env: TOXENV=py36-dj18-wag110
python: 3.6
- env: TOXENV=py36-dj18-wag112
python: 3.6
- env: TOXENV=py36-dj18-wag113
python: 3.6
- env: TOXENV=py36-dj111-wag110
python: 3.6
- env: TOXENV=py36-dj111-wag112
python: 3.6
- env: TOXENV=py36-dj111-wag113
python: 3.6
- env: TOXENV=py36-dj20-wag20
python: 3.6

install:
pip install tox coveralls
Expand Down
97 changes: 72 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Feature flags allow you to toggle functionality in both Django settings and the
- [API](#api)
- [Flag state](#flag-state)
- [Flag decorators](#flag-decorators)
- [Flagged URLs](#flagged-urls)
- [Flagged URL patterns](#flagged-url-patterns)
- [Django templates](#django-templates)
- [Jinja2 templates](#jinja2-templates)
- [Conditions](#conditions)
Expand All @@ -29,13 +29,13 @@ Feature flags allow you to toggle functionality in both Django settings and the

## Dependencies

- Django 1.8+
- Wagtail 1.10+
- Django 1.8+ (including Django 2.0)
- Wagtail 1.10+ (including Wagtail 2.0)
- Python 2.7+, 3.6+

## Installation

1. Install wagtail-flags using pip:
1. Install wagtail-flags:

```shell
pip install wagtail-flags
Expand Down Expand Up @@ -86,20 +86,32 @@ Then use the flag in a Django template (`mytemplate.html`):

Configure a URL for that template (`urls.py`):

Django 2.0:

```python
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
path(r'mypage/', TemplateView.as_view(template_name='mytemplate.html')),
]
```

Django 1.x:

```python
from django.conf.urls import url
from django.views.generic.base import TemplateView
from django.views.generic import TemplateView

urlpatterns = [
url(r'^/mypage$', TemplateView.as_view(template_name='mytemplate.html')),
url(r'^mypage/$', TemplateView.as_view(template_name='mytemplate.html')),
]
```

Then in the Wagtail admin add conditions for the flag in "Settings", "Flags":

![Creating conditions in the Wagtail admin](https://raw.githubusercontent.com/cfpb/wagtail-flags/master/screenshot_create.png)


Then visiting the URL `/mypage?enable_my_flag=True` should show you the flagged `<div>` in the template.

### Adding flags
Expand Down Expand Up @@ -155,18 +167,27 @@ Jinja2 templates (after [adding `flag_enabled` to the Jinja2 environment](#jinja
{% endif %}
```

And Django `urls.py`:
Django 2.0 `urls.py`:

```python
from flags.urls import flagged_url, flagged_urls
from flags.urls import flagged_path

urlpatterns = [
flagged_url('MY_FLAG', r'^an-url$', view_requiring_flag, state=True),
flagged_path('MY_FLAG', 'a-url/', view_requiring_flag, state=True),
]
```

See the [API documentation below](#api) for more details and examples.
And Django 1.x `urls.py`:

```python
from flags.urls import flagged_url

urlpatterns = [
flagged_url('MY_FLAG', r'^a-url$', view_requiring_flag, state=True),
]
```

See the [API documentation below](#api) for more details and examples.


#### Built-in conditions
Expand Down Expand Up @@ -318,42 +339,68 @@ def view_with_fallback(request):
return HttpResponse('flag was set')
```

### Flagged URLs
### Flagged URL patterns

Flagged URL patterns are an alternative to [flagging views with decorators](https://github.com/cfpb/wagtail-flags#flag_checkflag_name-state-fallbacknone-kwargs).

Django 2.0+:

```python
from flags.urls import flagged_url, flagged_urls
from flags.urls import flagged_path, flagged_paths, flagged_re_path, flagged_re_paths
```

Flagged URLs are an alternative to [flagging views with decorators](https://github.com/cfpb/wagtail-flags#flag_checkflag_name-state-fallbacknone-kwargs).
Django 1.x:

```python
from flags.urls import flagged_url, flagged_urls
```

#### `flagged_path(flag_name, route, view, kwargs=None, name=None, state=True, fallback=None)`
#### `flagged_re_path(flag_name, route, view, kwargs=None, name=None, state=True, fallback=None)`
#### `flagged_url(flag_name, regex, view, kwargs=None, name=None, state=True, fallback=None)`

Make a URL depend on the state of a feature flag. `flagged_url()` can be used in place of Django's `url()`.
Make a URL depend on the state of a feature flag.

`flagged_path()` can be used in place of [Django's `path()`](https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.path).

`flagged_re_path()` can be used in place of [Django's `re_path()`](https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path).

`flagged_url()` *is only available with Django 1.x* and can be used in place of [Django's `url()`](https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url).

The `view` and the `fallback` can both be a set of `include()`ed patterns but any matching URL patterns in the includes must match *exactly* in terms of regular expression, keyword arguments, and name, otherwise a `404` may be unexpectedly raised.

If a `fallback` is not given the flagged url will raise a `404` if the flag state does not match the required `state`.

```python
urlpatterns = [
flagged_url('MY_FLAG', r'^an-url$', view_requiring_flag, state=True),
flagged_url('MY_FLAG_WITH_FALLBACK', r'^another-url$', view_with_fallback,
state=True, fallback=other_view)
flagged_url('MY_FLAGGED_INCLUDE', r'^myapp$', include('myapp.urls'),
state=True, fallback=other_view)
flagged_url('MY_NEW_APP_FLAG', r'^mynewapp$', include('mynewapp.urls'),
state=True, fallback=include('myoldapp.urls'))
flagged_path('MY_FLAG', r'a-url/', view_requiring_flag, state=True),
flagged_re_path('MY_FLAG_WITH_FALLBACK', r'^another-url$',
view_with_fallback, state=True, fallback=other_view)
flagged_path('MY_FLAGGED_INCLUDE', 'myapp/', include('myapp.urls'),
state=True, fallback=other_view)
flagged_re_path('MY_NEW_APP_FLAG', r'^mynewapp$', include('mynewapp.urls'),
state=True, fallback=include('myoldapp.urls'))
]
```

#### `flagged_paths(flag_name, state=True, fallback=None)`
#### `flagged_re_paths(flag_name, state=True, fallback=None)`
#### `flagged_urls(flag_name, state=True, fallback=None)`

Flag multiple URLs in the same context. Returns function that can be used in place of Django's `url()` that wraps `flagged_url()`. Can take an optional fallback view that will apply to all urls.
Flag multiple URLs in the same context with a context manager.

`flagged_paths()` returns a function that takes the same arguments as [Django's `path()`](https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.path) and which will flag the pattern's view.

`flagged_re_paths()` returns a function that takes the same arguments as [Django's `re_path()`](https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path) and which will flag the pattern's view.

`flagged_urls()` *is only available with Django 1.x* and returns a function that takes the same arguments as [Django's `url()`](https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url).

Returns function that can be used in place of Django's `url()` that wraps `flagged_url()`. Can take an optional fallback view that will apply to all urls.

```python
with flagged_urls('MY_FLAG') as url:
with flagged_paths('MY_FLAG') as path:
flagged_url_patterns = [
url(r'^an-url$', view_requiring_flag),
path('a-url/', view_requiring_flag),
]

urlpatterns = urlpatterns + flagged_url_patterns
Expand Down
6 changes: 5 additions & 1 deletion flags/conditions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re

import django
from django.apps import apps
from django.core.exceptions import ObjectDoesNotExist
from django.utils import dateparse, timezone
Expand Down Expand Up @@ -82,7 +83,10 @@ def anonymous_condition(boolean_value, request=None, **kwargs):
raise RequiredForCondition("request is required for condition "
"'anonymous'")

return bool(boolean_value) == bool(request.user.is_anonymous())
if django.VERSION[0] >= 2:
return bool(boolean_value) == bool(request.user.is_anonymous)
else:
return bool(boolean_value) == bool(request.user.is_anonymous())


@register('parameter')
Expand Down
4 changes: 2 additions & 2 deletions flags/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('key', models.CharField(max_length=500)),
('enabled', models.BooleanField()),
('site', models.ForeignKey(to='wagtailcore.Site')),
('site', models.ForeignKey(to='wagtailcore.Site', on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
name='SiteSettings',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('default', models.BooleanField()),
('site', models.ForeignKey(to='wagtailcore.Site')),
('site', models.ForeignKey(to='wagtailcore.Site', on_delete=models.CASCADE)),
],
),
]
4 changes: 2 additions & 2 deletions flags/migrations/0002_auto_20151030_1401.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='flagstate',
name='flag',
field=models.ForeignKey(to='flags.Flag'),
field=models.ForeignKey(to='flags.Flag', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='flagstate',
name='site',
field=models.ForeignKey(to='wagtailcore.Site'),
field=models.ForeignKey(to='wagtailcore.Site', on_delete=models.CASCADE),
),
]
4 changes: 2 additions & 2 deletions flags/migrations/0007_unique_flag_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='flagstate',
name='flag',
field=models.ForeignKey(related_name='states', to='flags.Flag'),
field=models.ForeignKey(related_name='states', to='flags.Flag', on_delete=models.CASCADE),
),
migrations.AlterField(
model_name='flagstate',
name='site',
field=models.ForeignKey(related_name='flag_states', to='wagtailcore.Site'),
field=models.ForeignKey(related_name='flag_states', to='wagtailcore.Site', on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
name='flagstate',
Expand Down
12 changes: 8 additions & 4 deletions flags/templatetags/feature_flags.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import django
from django import template

from flags.state import (
Expand All @@ -8,16 +9,19 @@

register = template.Library()

if django.VERSION >= (1, 9):
simple_tag = register.simple_tag
else:
simple_tag = register.assignment_tag

# @register.simple_tag(takes_context=True)
@register.assignment_tag(takes_context=True)

@simple_tag(takes_context=True)
def flag_enabled(context, flag_name):
request = context['request']
return base_flag_enabled(flag_name, request=request)


# @register.simple_tag(takes_context=True)
@register.assignment_tag(takes_context=True)
@simple_tag(takes_context=True)
def flag_disabled(context, flag_name):
request = context['request']
return base_flag_disabled(flag_name, request=request)
15 changes: 14 additions & 1 deletion flags/tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import os

import wagtail


SECRET_KEY = 'not needed'

Expand All @@ -25,7 +27,18 @@
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'wagtail.wagtailcore',
)

if wagtail.VERSION[0] >= 2:
INSTALLED_APPS += (
'wagtail.core',
)
else:
INSTALLED_APPS += (
'wagtail.wagtailcore',
)

INSTALLED_APPS += (
'flags',
)

Expand Down
8 changes: 6 additions & 2 deletions flags/tests/test_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
from django.test import TestCase
from django.utils import timezone

from wagtail.wagtailcore.models import Site

from flags.conditions import (
CONDITIONS,
RequiredForCondition,
Expand All @@ -25,6 +23,12 @@
from flags.models import FlagState


try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site


class ConditionRegistryTestCase(TestCase):

def test_register_decorator(self):
Expand Down
8 changes: 6 additions & 2 deletions flags/tests/test_state.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from django.http import HttpRequest
from django.test import TestCase

from wagtail.wagtailcore.models import Site

from flags.models import FlagState
from flags.state import flag_disabled, flag_enabled, flag_state


try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site


class FlagStateTestCase(TestCase):
def setUp(self):
self.site = Site.objects.get(is_default_site=True)
Expand Down
8 changes: 6 additions & 2 deletions flags/tests/test_template_functions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from django.http import HttpRequest
from django.test import TestCase

from wagtail.wagtailcore.models import Site

from flags.template_functions import flag_disabled, flag_enabled


try:
from wagtail.core.models import Site
except ImportError:
from wagtail.wagtailcore.models import Site


class TemplateFunctionsTestCase(TestCase):
def setUp(self):
self.site = Site.objects.get(is_default_site=True)
Expand Down
Loading

0 comments on commit d588d68

Please sign in to comment.