Wagtail 3 changes
This commit is contained in:
committed by
nick.moreton
parent
dd4530203f
commit
c7eaec1315
25
.github/workflows/python-test.yml
vendored
25
.github/workflows/python-test.yml
vendored
@ -23,9 +23,6 @@ jobs:
|
|||||||
max-parallel: 4
|
max-parallel: 4
|
||||||
matrix:
|
matrix:
|
||||||
tox_env:
|
tox_env:
|
||||||
- py36-dj22-wt211
|
|
||||||
- py36-dj22-wt212
|
|
||||||
- py36-dj22-wt213
|
|
||||||
- py37-dj22-wt211
|
- py37-dj22-wt211
|
||||||
- py37-dj22-wt212
|
- py37-dj22-wt212
|
||||||
- py37-dj22-wt213
|
- py37-dj22-wt213
|
||||||
@ -39,12 +36,6 @@ jobs:
|
|||||||
- py38-dj30-wt212
|
- py38-dj30-wt212
|
||||||
- py38-dj30-wt213
|
- py38-dj30-wt213
|
||||||
include:
|
include:
|
||||||
- python-version: 3.6
|
|
||||||
tox_env: py36-dj22-wt211
|
|
||||||
- python-version: 3.6
|
|
||||||
tox_env: py36-dj22-wt212
|
|
||||||
- python-version: 3.6
|
|
||||||
tox_env: py36-dj22-wt213
|
|
||||||
- python-version: 3.7
|
- python-version: 3.7
|
||||||
tox_env: py37-dj22-wt211
|
tox_env: py37-dj22-wt211
|
||||||
- python-version: 3.7
|
- python-version: 3.7
|
||||||
@ -69,6 +60,22 @@ jobs:
|
|||||||
tox_env: py38-dj30-wt212
|
tox_env: py38-dj30-wt212
|
||||||
- python-version: 3.8
|
- python-version: 3.8
|
||||||
tox_env: py38-dj30-wt213
|
tox_env: py38-dj30-wt213
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj31-wt214
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj32-wt214
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj31-wt215
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj32-wt215
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj32-wt216
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj40-wt216
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj32-wt30
|
||||||
|
- python-version: 3.9
|
||||||
|
tox_env: py39-dj40-wt30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
6
Makefile
6
Makefile
@ -17,13 +17,13 @@ install: develop
|
|||||||
develop: clean requirements
|
develop: clean requirements
|
||||||
|
|
||||||
test:
|
test:
|
||||||
py.test --nomigrations --reuse-db tests/
|
py.test --reuse-db tests/
|
||||||
|
|
||||||
retest:
|
retest:
|
||||||
py.test --nomigrations --reuse-db tests/ -vvv
|
py.test --reuse-db tests/ -vvv
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
py.test --nomigrations --reuse-db tests/ --cov=wagtail_personalisation --cov-report=term-missing --cov-report=html
|
py.test --reuse-db tests/ --cov=wagtail_personalisation --cov-report=term-missing --cov-report=html
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
@ -17,7 +17,7 @@ A very simple example for a personalisable homepage:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from wagtail.wagtailcore.models import Page
|
from wagtail.models import Page
|
||||||
from wagtail_personalisation.models import PersonalisablePageMixin
|
from wagtail_personalisation.models import PersonalisablePageMixin
|
||||||
|
|
||||||
class HomePage(PersonalisablePageMixin, Page):
|
class HomePage(PersonalisablePageMixin, Page):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Django>=2.2,<2.3
|
Django>=2.2,<4.1
|
||||||
wagtail>=2.6,<2.7
|
wagtail>=2.9,<4.0
|
||||||
django-debug-toolbar==2.0
|
django-debug-toolbar==3.5.0
|
||||||
-e .[docs,test]
|
-e .[docs,test]
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
# Generated by Django 1.11.1 on 2017-05-31 16:59
|
# Generated by Django 1.11.1 on 2017-05-31 16:59
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import modelcluster.fields
|
import modelcluster.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -12,19 +12,29 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtailcore', '0033_remove_golive_expiry_help_text'),
|
("wagtailcore", "0033_remove_golive_expiry_help_text"),
|
||||||
('wagtail_personalisation', '0011_personalisablepagemetadata'),
|
("wagtail_personalisation", "0011_personalisablepagemetadata"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='HomePage',
|
name="HomePage",
|
||||||
fields=[
|
fields=[
|
||||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page', models.Model),
|
bases=("wagtailcore.page", models.Model),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -6,10 +6,10 @@ from django.db import migrations
|
|||||||
|
|
||||||
def create_homepage(apps, schema_editor):
|
def create_homepage(apps, schema_editor):
|
||||||
# Get models
|
# Get models
|
||||||
ContentType = apps.get_model('contenttypes.ContentType')
|
ContentType = apps.get_model("contenttypes.ContentType")
|
||||||
Page = apps.get_model('wagtailcore.Page')
|
Page = apps.get_model("wagtailcore.Page")
|
||||||
Site = apps.get_model('wagtailcore.Site')
|
Site = apps.get_model("wagtailcore.Site")
|
||||||
HomePage = apps.get_model('home.HomePage')
|
HomePage = apps.get_model("home.HomePage")
|
||||||
|
|
||||||
# Delete the default homepage
|
# Delete the default homepage
|
||||||
# If migration is run multiple times, it may have already been deleted
|
# If migration is run multiple times, it may have already been deleted
|
||||||
@ -17,41 +17,41 @@ def create_homepage(apps, schema_editor):
|
|||||||
|
|
||||||
# Create content type for homepage model
|
# Create content type for homepage model
|
||||||
homepage_content_type, __ = ContentType.objects.get_or_create(
|
homepage_content_type, __ = ContentType.objects.get_or_create(
|
||||||
model='homepage', app_label='home')
|
model="homepage", app_label="home"
|
||||||
|
)
|
||||||
|
|
||||||
# Create a new homepage
|
# Create a new homepage
|
||||||
homepage = HomePage.objects.create(
|
homepage = HomePage.objects.create(
|
||||||
title="Home",
|
title="Home",
|
||||||
slug='home',
|
slug="home",
|
||||||
content_type=homepage_content_type,
|
content_type=homepage_content_type,
|
||||||
path='00010001',
|
path="00010001",
|
||||||
depth=2,
|
depth=2,
|
||||||
numchild=0,
|
numchild=0,
|
||||||
url_path='/home/',
|
url_path="/home/",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a site with the new homepage set as the root
|
# Create a site with the new homepage set as the root
|
||||||
Site.objects.create(
|
Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True)
|
||||||
hostname='localhost', root_page=homepage, is_default_site=True)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_homepage(apps, schema_editor):
|
def remove_homepage(apps, schema_editor):
|
||||||
# Get models
|
# Get models
|
||||||
ContentType = apps.get_model('contenttypes.ContentType')
|
ContentType = apps.get_model("contenttypes.ContentType")
|
||||||
HomePage = apps.get_model('home.HomePage')
|
HomePage = apps.get_model("home.HomePage")
|
||||||
|
|
||||||
# Delete the default homepage
|
# Delete the default homepage
|
||||||
# Page and Site objects CASCADE
|
# Page and Site objects CASCADE
|
||||||
HomePage.objects.filter(slug='home', depth=2).delete()
|
HomePage.objects.filter(slug="home", depth=2).delete()
|
||||||
|
|
||||||
# Delete content type for homepage model
|
# Delete content type for homepage model
|
||||||
ContentType.objects.filter(model='homepage', app_label='home').delete()
|
ContentType.objects.filter(model="homepage", app_label="home").delete()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('home', '0001_initial'),
|
("home", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -3,28 +3,59 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import wagtail.core.fields
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
import wagtail_personalisation
|
import wagtail_personalisation
|
||||||
|
|
||||||
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail import blocks as wagtail_blocks
|
||||||
|
from wagtail import fields as wagtail_fields
|
||||||
|
else:
|
||||||
|
from wagtail.core import blocks as wagtail_blocks
|
||||||
|
from wagtail.core import fields as wagtail_fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('home', '0002_create_homepage'),
|
("home", "0002_create_homepage"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='homepage',
|
model_name="homepage",
|
||||||
name='intro',
|
name="intro",
|
||||||
field=wagtail.core.fields.RichTextField(
|
field=wagtail_fields.RichTextField(
|
||||||
default='<p>Thank you for trying <a href="http://wagxperience.io" target="_blank">Wagxperience</a>!</p>'),
|
default='<p>Thank you for trying <a href="http://wagxperience.io" target="_blank">Wagxperience</a>!</p>'
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='homepage',
|
model_name="homepage",
|
||||||
name='body',
|
name="body",
|
||||||
field=wagtail.core.fields.StreamField((('personalisable_paragraph', wagtail.core.blocks.StructBlock((('segment', wagtail.core.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('paragraph', wagtail.core.blocks.RichTextBlock())), icon='pilcrow')),), default=''),
|
field=wagtail_fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"personalisable_paragraph",
|
||||||
|
wagtail_blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
wagtail_blocks.ChoiceBlock(
|
||||||
|
choices=wagtail_personalisation.blocks.list_segment_choices,
|
||||||
|
help_text="Only show this content block for users in this segment",
|
||||||
|
label="Personalisation segment",
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail_blocks.RichTextBlock()),
|
||||||
|
),
|
||||||
|
icon="pilcrow",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
default="",
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,23 +1,45 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from wagtail.admin.edit_handlers import RichTextFieldPanel, StreamFieldPanel
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
from wagtail.core import blocks
|
|
||||||
from wagtail.core.fields import RichTextField, StreamField
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
from wagtail.core.models import Page
|
from wagtail import blocks
|
||||||
|
from wagtail.admin.panels import FieldPanel
|
||||||
|
from wagtail.fields import RichTextField, StreamField
|
||||||
|
from wagtail.models import Page
|
||||||
|
else:
|
||||||
|
from wagtail.admin.edit_handlers import RichTextFieldPanel, StreamFieldPanel
|
||||||
|
from wagtail.core import blocks
|
||||||
|
from wagtail.core.fields import RichTextField, StreamField
|
||||||
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from wagtail_personalisation.models import PersonalisablePageMixin
|
|
||||||
from wagtail_personalisation.blocks import PersonalisedStructBlock
|
from wagtail_personalisation.blocks import PersonalisedStructBlock
|
||||||
|
from wagtail_personalisation.models import PersonalisablePageMixin
|
||||||
|
|
||||||
|
|
||||||
class HomePage(PersonalisablePageMixin, Page):
|
class HomePage(PersonalisablePageMixin, Page):
|
||||||
intro = RichTextField()
|
intro = RichTextField()
|
||||||
body = StreamField([
|
body = StreamField(
|
||||||
('personalisable_paragraph', PersonalisedStructBlock([
|
[
|
||||||
('paragraph', blocks.RichTextBlock()),
|
(
|
||||||
], icon='pilcrow'))
|
"personalisable_paragraph",
|
||||||
])
|
PersonalisedStructBlock(
|
||||||
|
[
|
||||||
|
("paragraph", blocks.RichTextBlock()),
|
||||||
|
],
|
||||||
|
icon="pilcrow",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
RichTextFieldPanel('intro'),
|
content_panels = Page.content_panels + [
|
||||||
StreamFieldPanel('body'),
|
FieldPanel("intro"),
|
||||||
]
|
FieldPanel("body"),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
RichTextFieldPanel("intro"),
|
||||||
|
StreamFieldPanel("body"),
|
||||||
|
]
|
||||||
|
@ -2,14 +2,19 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.models import Page
|
||||||
|
else:
|
||||||
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from wagtail.core.models import Page
|
|
||||||
from wagtail.search.models import Query
|
from wagtail.search.models import Query
|
||||||
|
|
||||||
|
|
||||||
def search(request):
|
def search(request):
|
||||||
search_query = request.GET.get('query', None)
|
search_query = request.GET.get("query", None)
|
||||||
page = request.GET.get('page', 1)
|
page = request.GET.get("page", 1)
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
if search_query:
|
if search_query:
|
||||||
@ -30,7 +35,11 @@ def search(request):
|
|||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
search_results = paginator.page(paginator.num_pages)
|
search_results = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
return render(request, 'search/search.html', {
|
return render(
|
||||||
'search_query': search_query,
|
request,
|
||||||
'search_results': search_results,
|
"search/search.html",
|
||||||
})
|
{
|
||||||
|
"search_query": search_query,
|
||||||
|
"search_results": search_results,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@ -12,31 +12,30 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
# The fields to be used in displaying the User model.
|
# The fields to be used in displaying the User model.
|
||||||
# These override the definitions on the base UserAdmin
|
# These override the definitions on the base UserAdmin
|
||||||
# that reference specific fields on auth.User.
|
# that reference specific fields on auth.User.
|
||||||
list_display = ['email']
|
list_display = ["email"]
|
||||||
list_filter = ['is_superuser']
|
list_filter = ["is_superuser"]
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {"fields": ["email", "password"]}),
|
||||||
'fields': ['email', 'password']
|
("Personal info", {"fields": ["first_name", "last_name"]}),
|
||||||
}),
|
(
|
||||||
('Personal info', {
|
"Permissions",
|
||||||
'fields': ['first_name', 'last_name']
|
{
|
||||||
}),
|
"fields": [
|
||||||
('Permissions', {
|
"is_active",
|
||||||
'fields': [
|
"is_staff",
|
||||||
'is_active', 'is_staff', 'is_superuser',
|
"is_superuser",
|
||||||
'groups', 'user_permissions'
|
"groups",
|
||||||
]
|
"user_permissions",
|
||||||
}),
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
|
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
|
||||||
# overrides get_fieldsets to use this attribute when creating a user.
|
# overrides get_fieldsets to use this attribute when creating a user.
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {"classes": ("wide",), "fields": ["email", "password1", "password2"]}),
|
||||||
'classes': ('wide',),
|
|
||||||
'fields': ['email', 'password1', 'password2']
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
search_fields = ['first_name', 'last_name', 'email']
|
search_fields = ["first_name", "last_name", "email"]
|
||||||
ordering = ['email']
|
ordering = ["email"]
|
||||||
filter_horizontal = []
|
filter_horizontal = []
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from sandbox.apps.user import models
|
from sandbox.apps.user import models
|
||||||
|
|
||||||
@ -10,21 +15,22 @@ class UserCreationForm(forms.ModelForm):
|
|||||||
fields, plus a repeated password.
|
fields, plus a repeated password.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
password1 = forms.CharField(
|
password1 = forms.CharField(
|
||||||
label='Password', widget=forms.PasswordInput,
|
label="Password", widget=forms.PasswordInput, required=False
|
||||||
required=False)
|
)
|
||||||
password2 = forms.CharField(
|
password2 = forms.CharField(
|
||||||
label='Password confirmation', widget=forms.PasswordInput,
|
label="Password confirmation", widget=forms.PasswordInput, required=False
|
||||||
required=False)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = ['email']
|
fields = ["email"]
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
# Check that the two password entries match
|
# Check that the two password entries match
|
||||||
password1 = self.cleaned_data.get('password1')
|
password1 = self.cleaned_data.get("password1")
|
||||||
password2 = self.cleaned_data.get('password2')
|
password2 = self.cleaned_data.get("password2")
|
||||||
if password1 and password2 and password1 != password2:
|
if password1 and password2 and password1 != password2:
|
||||||
raise forms.ValidationError("Passwords don't match")
|
raise forms.ValidationError("Passwords don't match")
|
||||||
return password2
|
return password2
|
||||||
@ -32,7 +38,7 @@ class UserCreationForm(forms.ModelForm):
|
|||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
# Save the provided password in hashed format
|
# Save the provided password in hashed format
|
||||||
user = super(UserCreationForm, self).save(commit=False)
|
user = super(UserCreationForm, self).save(commit=False)
|
||||||
user.set_password(self.cleaned_data['password1'])
|
user.set_password(self.cleaned_data["password1"])
|
||||||
if commit:
|
if commit:
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
@ -44,20 +50,22 @@ class UserChangeForm(forms.ModelForm):
|
|||||||
password hash display field.
|
password hash display field.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
password = ReadOnlyPasswordHashField(
|
password = ReadOnlyPasswordHashField(
|
||||||
label=_("Password"),
|
label=_("Password"),
|
||||||
help_text=_("Raw passwords are not stored, so there is no way to see "
|
help_text=_(
|
||||||
"this user's password, but you can change the password "
|
"Raw passwords are not stored, so there is no way to see "
|
||||||
"using <a href=\"password/\">this form</a>."))
|
"this user's password, but you can change the password "
|
||||||
|
'using <a href="password/">this form</a>.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = [
|
fields = ["email", "password", "is_active", "is_superuser"]
|
||||||
'email', 'password', 'is_active', 'is_superuser'
|
|
||||||
]
|
|
||||||
|
|
||||||
def clean_password(self):
|
def clean_password(self):
|
||||||
# Regardless of what the user provides, return the initial value.
|
# Regardless of what the user provides, return the initial value.
|
||||||
# This is done here, rather than on the field, because the
|
# This is done here, rather than on the field, because the
|
||||||
# field does not have access to the initial value
|
# field does not have access to the initial value
|
||||||
return self.initial['password']
|
return self.initial["password"]
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
from django.db import migrations, models
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -12,32 +12,109 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0008_alter_user_username_max_length'),
|
("auth", "0008_alter_user_username_max_length"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
"id",
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
models.AutoField(
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
auto_created=True,
|
||||||
('first_name', models.CharField(blank=True, max_length=100, verbose_name='first name')),
|
primary_key=True,
|
||||||
('last_name', models.CharField(blank=True, max_length=100, verbose_name='last name')),
|
serialize=False,
|
||||||
('email', models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email address')),
|
verbose_name="ID",
|
||||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
),
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
(
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
"last_login",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="last login"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_superuser",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||||
|
verbose_name="superuser status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"first_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=100, verbose_name="first name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"last_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=100, verbose_name="last name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True,
|
||||||
|
max_length=254,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="email address",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_staff",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates whether the user can log into this admin site.",
|
||||||
|
verbose_name="staff status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_active",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||||
|
verbose_name="active",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_joined",
|
||||||
|
models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now, verbose_name="date joined"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"groups",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Group",
|
||||||
|
verbose_name="groups",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user_permissions",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Specific permissions for this user.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Permission",
|
||||||
|
verbose_name="user permissions",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'user',
|
"verbose_name": "user",
|
||||||
'verbose_name_plural': 'users',
|
"verbose_name_plural": "users",
|
||||||
},
|
},
|
||||||
managers=[
|
managers=[
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
("objects", django.contrib.auth.models.UserManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-15 12:54
|
# Generated by Django 2.1.7 on 2019-03-15 12:54
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
import sandbox.apps.user.models
|
import sandbox.apps.user.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('user', '0001_initial'),
|
("user", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelManagers(
|
migrations.AlterModelManagers(
|
||||||
name='user',
|
name="user",
|
||||||
managers=[
|
managers=[
|
||||||
('objects', sandbox.apps.user.models.UserManager()),
|
("objects", sandbox.apps.user.models.UserManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
20
sandbox/sandbox/apps/user/migrations/0003_alter_user_id.py
Normal file
20
sandbox/sandbox/apps/user/migrations/0003_alter_user_id.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 4.0.5 on 2022-06-30 15:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("user", "0002_auto_20190315_1254"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="user",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1,9 +1,17 @@
|
|||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django.contrib.auth.models import (
|
from django.contrib.auth.models import (
|
||||||
AbstractBaseUser, PermissionsMixin, BaseUserManager)
|
AbstractBaseUser,
|
||||||
|
BaseUserManager,
|
||||||
|
PermissionsMixin,
|
||||||
|
)
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
@ -14,7 +22,7 @@ class UserManager(BaseUserManager):
|
|||||||
Create and save a user with the given username, email, and password.
|
Create and save a user with the given username, email, and password.
|
||||||
"""
|
"""
|
||||||
if not email:
|
if not email:
|
||||||
raise ValueError('The given email address must be set')
|
raise ValueError("The given email address must be set")
|
||||||
email = self.normalize_email(email)
|
email = self.normalize_email(email)
|
||||||
user = self.model(email=email, **extra_fields)
|
user = self.model(email=email, **extra_fields)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
@ -22,52 +30,56 @@ class UserManager(BaseUserManager):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
def create_user(self, email, password=None, **extra_fields):
|
def create_user(self, email, password=None, **extra_fields):
|
||||||
extra_fields.setdefault('is_staff', False)
|
extra_fields.setdefault("is_staff", False)
|
||||||
extra_fields.setdefault('is_superuser', False)
|
extra_fields.setdefault("is_superuser", False)
|
||||||
return self._create_user(email, password, **extra_fields)
|
return self._create_user(email, password, **extra_fields)
|
||||||
|
|
||||||
def create_superuser(self, email, password, **extra_fields):
|
def create_superuser(self, email, password, **extra_fields):
|
||||||
extra_fields.setdefault('is_staff', True)
|
extra_fields.setdefault("is_staff", True)
|
||||||
extra_fields.setdefault('is_superuser', True)
|
extra_fields.setdefault("is_superuser", True)
|
||||||
|
|
||||||
if extra_fields.get('is_staff') is not True:
|
if extra_fields.get("is_staff") is not True:
|
||||||
raise ValueError('Superuser must have is_staff=True.')
|
raise ValueError("Superuser must have is_staff=True.")
|
||||||
if extra_fields.get('is_superuser') is not True:
|
if extra_fields.get("is_superuser") is not True:
|
||||||
raise ValueError('Superuser must have is_superuser=True.')
|
raise ValueError("Superuser must have is_superuser=True.")
|
||||||
return self._create_user(email, password, **extra_fields)
|
return self._create_user(email, password, **extra_fields)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser, PermissionsMixin):
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
"""Customized version of the default `AbstractUser` from Django.
|
"""Customized version of the default `AbstractUser` from Django."""
|
||||||
|
|
||||||
"""
|
first_name = models.CharField(_("first name"), max_length=100, blank=True)
|
||||||
first_name = models.CharField(_('first name'), max_length=100, blank=True)
|
last_name = models.CharField(_("last name"), max_length=100, blank=True)
|
||||||
last_name = models.CharField(_('last name'), max_length=100, blank=True)
|
email = models.EmailField(_("email address"), blank=True, unique=True)
|
||||||
email = models.EmailField(_('email address'), blank=True, unique=True)
|
|
||||||
is_staff = models.BooleanField(
|
is_staff = models.BooleanField(
|
||||||
_('staff status'), default=False,
|
_("staff status"),
|
||||||
help_text=_('Designates whether the user can log into this admin '
|
default=False,
|
||||||
'site.'))
|
help_text=_("Designates whether the user can log into this admin " "site."),
|
||||||
|
)
|
||||||
is_active = models.BooleanField(
|
is_active = models.BooleanField(
|
||||||
_('active'), default=True,
|
_("active"),
|
||||||
help_text=_('Designates whether this user should be treated as '
|
default=True,
|
||||||
'active. Unselect this instead of deleting accounts.'))
|
help_text=_(
|
||||||
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
|
"Designates whether this user should be treated as "
|
||||||
|
"active. Unselect this instead of deleting accounts."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
USERNAME_FIELD = 'email'
|
USERNAME_FIELD = "email"
|
||||||
REQUIRED_FIELDS = []
|
REQUIRED_FIELDS = []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('user')
|
verbose_name = _("user")
|
||||||
verbose_name_plural = _('users')
|
verbose_name_plural = _("users")
|
||||||
|
|
||||||
def get_full_name(self):
|
def get_full_name(self):
|
||||||
"""
|
"""
|
||||||
Returns the first_name plus the last_name, with a space in between.
|
Returns the first_name plus the last_name, with a space in between.
|
||||||
"""
|
"""
|
||||||
full_name = '%s %s' % (self.first_name, self.last_name)
|
full_name = "%s %s" % (self.first_name, self.last_name)
|
||||||
return full_name.strip()
|
return full_name.strip()
|
||||||
|
|
||||||
def get_short_name(self):
|
def get_short_name(self):
|
||||||
|
@ -14,7 +14,9 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
import os
|
import os
|
||||||
from importlib.util import find_spec
|
|
||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
BASE_DIR = os.path.dirname(PROJECT_DIR)
|
BASE_DIR = os.path.dirname(PROJECT_DIR)
|
||||||
@ -22,10 +24,10 @@ BASE_DIR = os.path.dirname(PROJECT_DIR)
|
|||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = '^anfvx$i7%wts8j=7u1h5ua$w6c76*333(@h)rrjlak1c&x0r+'
|
SECRET_KEY = "^anfvx$i7%wts8j=7u1h5ua$w6c76*333(@h)rrjlak1c&x0r+"
|
||||||
|
|
||||||
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||||
@ -35,90 +37,80 @@ SITE_ID = 1
|
|||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
"django.contrib.admin",
|
||||||
'django.contrib.auth',
|
"django.contrib.auth",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.messages',
|
"django.contrib.messages",
|
||||||
'django.contrib.sessions',
|
"django.contrib.sessions",
|
||||||
'django.contrib.sites',
|
"django.contrib.sites",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.staticfiles",
|
||||||
|
"wagtail.contrib.forms",
|
||||||
'wagtail.contrib.forms',
|
"wagtail.contrib.redirects",
|
||||||
'wagtail.contrib.redirects',
|
"wagtail.embeds",
|
||||||
'wagtail.embeds',
|
"wagtail.sites",
|
||||||
'wagtail.sites',
|
"wagtail.users",
|
||||||
'wagtail.users',
|
"wagtail.snippets",
|
||||||
'wagtail.snippets',
|
"wagtail.documents",
|
||||||
'wagtail.documents',
|
"wagtail.images",
|
||||||
'wagtail.images',
|
"wagtail.search",
|
||||||
'wagtail.search',
|
"wagtail.admin",
|
||||||
'wagtail.admin',
|
"wagtail" if WAGTAIL_VERSION >= (3, 0) else "wagtail.core",
|
||||||
'wagtail.core',
|
"wagtail.contrib.modeladmin",
|
||||||
'wagtail.contrib.modeladmin',
|
"wagtailfontawesome",
|
||||||
|
"modelcluster",
|
||||||
'wagtailfontawesome',
|
"taggit",
|
||||||
'modelcluster',
|
"debug_toolbar",
|
||||||
'taggit',
|
"wagtail_personalisation",
|
||||||
'debug_toolbar',
|
"sandbox.apps.home",
|
||||||
|
"sandbox.apps.search",
|
||||||
'wagtail_personalisation',
|
"sandbox.apps.user",
|
||||||
|
|
||||||
'sandbox.apps.home',
|
|
||||||
'sandbox.apps.search',
|
|
||||||
'sandbox.apps.user',
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.middleware.http.ConditionalGetMiddleware',
|
"django.middleware.http.ConditionalGetMiddleware",
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
'wagtail.contrib.redirects.middleware.RedirectMiddleware',
|
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
if find_spec('wagtail.contrib.legacy'):
|
ROOT_URLCONF = "sandbox.urls"
|
||||||
MIDDLEWARE += ('wagtail.contrib.legacy.sitemiddleware.SiteMiddleware',)
|
|
||||||
else:
|
|
||||||
MIDDLEWARE += ('wagtail.core.middleware.SiteMiddleware', )
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'sandbox.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [
|
"DIRS": [
|
||||||
os.path.join(PROJECT_DIR, 'templates'),
|
os.path.join(PROJECT_DIR, "templates"),
|
||||||
],
|
],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'sandbox.wsgi.application'
|
WSGI_APPLICATION = "sandbox.wsgi.application"
|
||||||
|
|
||||||
AUTH_USER_MODEL = 'user.User'
|
AUTH_USER_MODEL = "user.User"
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
'NAME': 'db.sqlite3',
|
"NAME": "db.sqlite3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,9 +118,9 @@ DATABASES = {
|
|||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
@ -141,19 +133,19 @@ USE_TZ = True
|
|||||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||||
|
|
||||||
STATICFILES_FINDERS = [
|
STATICFILES_FINDERS = [
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||||
]
|
]
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
os.path.join(PROJECT_DIR, 'static'),
|
os.path.join(PROJECT_DIR, "static"),
|
||||||
]
|
]
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
|
||||||
# Wagtail settings
|
# Wagtail settings
|
||||||
@ -162,7 +154,13 @@ WAGTAIL_SITE_NAME = "sandbox"
|
|||||||
|
|
||||||
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
# Base URL to use when referring to full URLs within the Wagtail admin backend -
|
||||||
# e.g. in notification emails. Don't include '/admin' or a trailing slash
|
# e.g. in notification emails. Don't include '/admin' or a trailing slash
|
||||||
BASE_URL = 'http://example.com'
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
WAGTAILADMIN_BASE_URL = "http://example.com"
|
||||||
|
else:
|
||||||
|
BASE_URL = "http://example.com"
|
||||||
|
|
||||||
|
|
||||||
INTERNAL_IPS = ['127.0.0.1']
|
INTERNAL_IPS = ["127.0.0.1"]
|
||||||
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
@ -2,8 +2,8 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import include, re_path
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
from wagtail.core import urls as wagtail_urls
|
from wagtail.core import urls as wagtail_urls
|
||||||
from wagtail.documents import urls as wagtaildocs_urls
|
from wagtail.documents import urls as wagtaildocs_urls
|
||||||
@ -11,18 +11,14 @@ from wagtail.documents import urls as wagtaildocs_urls
|
|||||||
from sandbox.apps.search import views as search_views
|
from sandbox.apps.search import views as search_views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^django-admin/', admin.site.urls),
|
re_path(r"^django-admin/", admin.site.urls),
|
||||||
|
re_path(r"^admin/", include(wagtailadmin_urls)),
|
||||||
url(r'^admin/', include(wagtailadmin_urls)),
|
re_path(r"^documents/", include(wagtaildocs_urls)),
|
||||||
url(r'^documents/', include(wagtaildocs_urls)),
|
re_path(r"^search/$", search_views.search, name="search"),
|
||||||
|
|
||||||
url(r'^search/$', search_views.search, name='search'),
|
|
||||||
|
|
||||||
# For anything not caught by a more specific rule above, hand over to
|
# For anything not caught by a more specific rule above, hand over to
|
||||||
# Wagtail's page serving mechanism. This should be the last pattern in
|
# Wagtail's page serving mechanism. This should be the last pattern in
|
||||||
# the list:
|
# the list:
|
||||||
url(r'', include(wagtail_urls)),
|
re_path(r"", include(wagtail_urls)),
|
||||||
|
|
||||||
# Alternatively, if you want Wagtail pages to be served from a subpath
|
# Alternatively, if you want Wagtail pages to be served from a subpath
|
||||||
# of your site, rather than the site root:
|
# of your site, rather than the site root:
|
||||||
# url(r'^pages/', include(wagtail_urls)),
|
# url(r'^pages/', include(wagtail_urls)),
|
||||||
@ -38,5 +34,5 @@ if settings.DEBUG:
|
|||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
re_path(r"^__debug__/", include(debug_toolbar.urls)),
|
||||||
] + urlpatterns
|
] + urlpatterns
|
||||||
|
79
setup.py
79
setup.py
@ -1,37 +1,39 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
'wagtail>=2.0',
|
"wagtail>=2.9,<4.0",
|
||||||
'user-agents>=1.1.0',
|
"user-agents>=2.2.0",
|
||||||
'wagtailfontawesome>=1.1.3',
|
"wagtailfontawesome>=1.2.1",
|
||||||
'pycountry',
|
"pycountry",
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
'factory_boy==2.8.1',
|
"factory_boy==3.2.1",
|
||||||
'flake8-blind-except',
|
"flake8-blind-except",
|
||||||
'flake8-debugger',
|
"flake8-debugger",
|
||||||
'flake8-isort',
|
"flake8-isort",
|
||||||
'flake8',
|
"flake8",
|
||||||
'freezegun==0.3.8',
|
"freezegun==1.2.1",
|
||||||
'pytest-cov==2.5.1',
|
"pytest-cov==3.0.0",
|
||||||
'pytest-django==4.1.0',
|
"pytest-django==4.5.2",
|
||||||
'pytest-pythonpath==0.7.2',
|
"pytest-pythonpath==0.7.4",
|
||||||
'pytest-sugar==0.9.1',
|
"pytest-sugar==0.9.4",
|
||||||
'pytest==6.1.2',
|
"pytest==6.2.5",
|
||||||
'wagtail_factories==1.1.0',
|
"wagtail_factories",
|
||||||
'pytest-mock==1.6.3',
|
"pytest-mock==3.8.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
docs_require = [
|
docs_require = [
|
||||||
'sphinx>=1.7.6',
|
"sphinx>=1.7.6",
|
||||||
'sphinx_rtd_theme>=0.4.0',
|
"sphinx_rtd_theme>=0.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
with open('README.rst') as fh:
|
with open("README.rst") as fh:
|
||||||
long_description = re.sub(
|
long_description = re.sub(
|
||||||
'^.. start-no-pypi.*^.. end-no-pypi', '', fh.read(), flags=re.M | re.S)
|
"^.. start-no-pypi.*^.. end-no-pypi", "", fh.read(), flags=re.M | re.S
|
||||||
|
)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='wagtail-personalisation',
|
name='wagtail-personalisation',
|
||||||
@ -43,24 +45,29 @@ setup(
|
|||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
extras_require={
|
extras_require={
|
||||||
'docs': docs_require,
|
"docs": docs_require,
|
||||||
'test': tests_require,
|
"test": tests_require,
|
||||||
},
|
},
|
||||||
packages=find_packages('src'),
|
packages=find_packages("src"),
|
||||||
package_dir={'': 'src'},
|
package_dir={"": "src"},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license='MIT',
|
license="MIT",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
"Development Status :: 5 - Production/Stable",
|
||||||
'Environment :: Web Environment',
|
"Environment :: Web Environment",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'License :: OSI Approved :: BSD License',
|
"License :: OSI Approved :: BSD License",
|
||||||
'Operating System :: OS Independent',
|
"Operating System :: OS Independent",
|
||||||
'Programming Language :: Python :: 3',
|
"Programming Language :: Python :: 3",
|
||||||
'Programming Language :: Python :: 3.6',
|
"Programming Language :: Python :: 3.7",
|
||||||
'Framework :: Django',
|
"Programming Language :: Python :: 3.8",
|
||||||
'Framework :: Django :: 2.0',
|
"Programming Language :: Python :: 3.9",
|
||||||
'Topic :: Internet :: WWW/HTTP :: Site Management',
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Framework :: Django",
|
||||||
|
"Framework :: Django :: 2.2",
|
||||||
|
"Framework :: Django :: 3.0",
|
||||||
|
"Framework :: Django :: 4.0",
|
||||||
|
"Topic :: Internet :: WWW/HTTP :: Site Management",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -1 +1 @@
|
|||||||
default_app_config = 'wagtail_personalisation.config.WagtailPersonalisationConfig'
|
default_app_config = "wagtail_personalisation.config.WagtailPersonalisationConfig"
|
||||||
|
@ -61,18 +61,13 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
|||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
super(SessionSegmentsAdapter, self).__init__(request)
|
super(SessionSegmentsAdapter, self).__init__(request)
|
||||||
self.request.session.setdefault('segments', [])
|
self.request.session.setdefault("segments", [])
|
||||||
self._segment_cache = None
|
self._segment_cache = None
|
||||||
|
|
||||||
def _segments(self, ids=None):
|
def _segments(self, ids=None):
|
||||||
if not ids:
|
if not ids:
|
||||||
ids = []
|
ids = []
|
||||||
segments = (
|
segments = Segment.objects.enabled().filter(persistent=True).filter(pk__in=ids)
|
||||||
Segment.objects
|
|
||||||
.enabled()
|
|
||||||
.filter(persistent=True)
|
|
||||||
.filter(pk__in=ids)
|
|
||||||
)
|
|
||||||
return segments
|
return segments
|
||||||
|
|
||||||
def get_segments(self, key="segments"):
|
def get_segments(self, key="segments"):
|
||||||
@ -90,7 +85,7 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
|||||||
if key not in self.request.session:
|
if key not in self.request.session:
|
||||||
return []
|
return []
|
||||||
raw_segments = self.request.session[key]
|
raw_segments = self.request.session[key]
|
||||||
segment_ids = [segment['id'] for segment in raw_segments]
|
segment_ids = [segment["id"] for segment in raw_segments]
|
||||||
|
|
||||||
segments = self._segments(ids=segment_ids)
|
segments = self._segments(ids=segment_ids)
|
||||||
|
|
||||||
@ -113,7 +108,7 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
|||||||
segment_ids = set()
|
segment_ids = set()
|
||||||
for segment in segments:
|
for segment in segments:
|
||||||
serialized = create_segment_dictionary(segment)
|
serialized = create_segment_dictionary(segment)
|
||||||
if serialized['id'] in segment_ids:
|
if serialized["id"] in segment_ids:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cache_segments.append(segment)
|
cache_segments.append(segment)
|
||||||
@ -139,41 +134,44 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
|||||||
|
|
||||||
def add_page_visit(self, page):
|
def add_page_visit(self, page):
|
||||||
"""Mark the page as visited by the user"""
|
"""Mark the page as visited by the user"""
|
||||||
visit_count = self.request.session.setdefault('visit_count', [])
|
visit_count = self.request.session.setdefault("visit_count", [])
|
||||||
page_visits = [visit for visit in visit_count if visit['id'] == page.pk]
|
page_visits = [visit for visit in visit_count if visit["id"] == page.pk]
|
||||||
|
|
||||||
if page_visits:
|
if page_visits:
|
||||||
for page_visit in page_visits:
|
for page_visit in page_visits:
|
||||||
page_visit['count'] += 1
|
page_visit["count"] += 1
|
||||||
page_visit['path'] = page.url_path if page else self.request.path
|
page_visit["path"] = page.url_path if page else self.request.path
|
||||||
self.request.session.modified = True
|
self.request.session.modified = True
|
||||||
else:
|
else:
|
||||||
visit_count.append({
|
visit_count.append(
|
||||||
'slug': page.slug,
|
{
|
||||||
'id': page.pk,
|
"slug": page.slug,
|
||||||
'path': page.url_path if page else self.request.path,
|
"id": page.pk,
|
||||||
'count': 1,
|
"path": page.url_path if page else self.request.path,
|
||||||
})
|
"count": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def get_visit_count(self, page=None):
|
def get_visit_count(self, page=None):
|
||||||
"""Return the number of visits on the current request or given page"""
|
"""Return the number of visits on the current request or given page"""
|
||||||
path = page.url_path if page else self.request.path
|
path = page.url_path if page else self.request.path
|
||||||
visit_count = self.request.session.setdefault('visit_count', [])
|
visit_count = self.request.session.setdefault("visit_count", [])
|
||||||
for visit in visit_count:
|
for visit in visit_count:
|
||||||
if visit['path'] == path:
|
if visit["path"] == path:
|
||||||
return visit['count']
|
return visit["count"]
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def update_visit_count(self):
|
def update_visit_count(self):
|
||||||
"""Update the visit count for all segments in the request session."""
|
"""Update the visit count for all segments in the request session."""
|
||||||
segments = self.request.session['segments']
|
segments = self.request.session["segments"]
|
||||||
segment_pks = [s['id'] for s in segments]
|
segment_pks = [s["id"] for s in segments]
|
||||||
|
|
||||||
# Update counts
|
# Update counts
|
||||||
(Segment.objects
|
(
|
||||||
.enabled()
|
Segment.objects.enabled()
|
||||||
.filter(pk__in=segment_pks)
|
.filter(pk__in=segment_pks)
|
||||||
.update(visit_count=F('visit_count') + 1))
|
.update(visit_count=F("visit_count") + 1)
|
||||||
|
)
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
"""Retrieve the request session segments and verify whether or not they
|
"""Retrieve the request session segments and verify whether or not they
|
||||||
@ -185,27 +183,31 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
|||||||
|
|
||||||
current_segments = self.get_segments()
|
current_segments = self.get_segments()
|
||||||
excluded_segments = self.get_segments("excluded_segments")
|
excluded_segments = self.get_segments("excluded_segments")
|
||||||
current_segments = list(
|
current_segments = list(set(current_segments) - set(excluded_segments))
|
||||||
set(current_segments) - set(excluded_segments)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run tests on all remaining enabled segments to verify applicability.
|
# Run tests on all remaining enabled segments to verify applicability.
|
||||||
additional_segments = []
|
additional_segments = []
|
||||||
for segment in enabled_segments:
|
for segment in enabled_segments:
|
||||||
if segment.is_static and segment.static_users.filter(id=self.request.user.id).exists():
|
if (
|
||||||
|
segment.is_static
|
||||||
|
and segment.static_users.filter(id=self.request.user.id).exists()
|
||||||
|
):
|
||||||
additional_segments.append(segment)
|
additional_segments.append(segment)
|
||||||
elif any((
|
elif any(
|
||||||
segment.excluded_users.filter(id=self.request.user.id).exists(),
|
(
|
||||||
segment in excluded_segments
|
segment.excluded_users.filter(id=self.request.user.id).exists(),
|
||||||
)):
|
segment in excluded_segments,
|
||||||
|
)
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
elif not segment.is_static or not segment.is_full:
|
elif not segment.is_static or not segment.is_full:
|
||||||
segment_rules = []
|
segment_rules = []
|
||||||
for rule_model in rule_models:
|
for rule_model in rule_models:
|
||||||
segment_rules.extend(rule_model.objects.filter(segment=segment))
|
segment_rules.extend(rule_model.objects.filter(segment=segment))
|
||||||
|
|
||||||
result = self._test_rules(segment_rules, self.request,
|
result = self._test_rules(
|
||||||
match_any=segment.match_any)
|
segment_rules, self.request, match_any=segment.match_any
|
||||||
|
)
|
||||||
|
|
||||||
if result and segment.randomise_into_segment():
|
if result and segment.randomise_into_segment():
|
||||||
if segment.is_static and not segment.is_full:
|
if segment.is_static and not segment.is_full:
|
||||||
@ -223,14 +225,17 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
|||||||
self.update_visit_count()
|
self.update_visit_count()
|
||||||
|
|
||||||
|
|
||||||
SEGMENT_ADAPTER_CLASS = import_string(getattr(
|
SEGMENT_ADAPTER_CLASS = import_string(
|
||||||
settings,
|
getattr(
|
||||||
'PERSONALISATION_SEGMENTS_ADAPTER',
|
settings,
|
||||||
'wagtail_personalisation.adapters.SessionSegmentsAdapter'))
|
"PERSONALISATION_SEGMENTS_ADAPTER",
|
||||||
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_segment_adapter(request):
|
def get_segment_adapter(request):
|
||||||
"""Return the Segment Adapter for the given request"""
|
"""Return the Segment Adapter for the given request"""
|
||||||
if not hasattr(request, 'segment_adapter'):
|
if not hasattr(request, "segment_adapter"):
|
||||||
request.segment_adapter = SEGMENT_ADAPTER_CLASS(request)
|
request.segment_adapter = SEGMENT_ADAPTER_CLASS(request)
|
||||||
return request.segment_adapter
|
return request.segment_adapter
|
||||||
|
@ -8,6 +8,7 @@ class UserIsLoggedInRuleAdminInline(admin.TabularInline):
|
|||||||
administration interface for segments.
|
administration interface for segments.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = rules.UserIsLoggedInRule
|
model = rules.UserIsLoggedInRule
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ class TimeRuleAdminInline(admin.TabularInline):
|
|||||||
administration interface for segments.
|
administration interface for segments.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = rules.TimeRule
|
model = rules.TimeRule
|
||||||
|
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ class ReferralRuleAdminInline(admin.TabularInline):
|
|||||||
administration interface for segments.
|
administration interface for segments.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = rules.ReferralRule
|
model = rules.ReferralRule
|
||||||
|
|
||||||
|
|
||||||
@ -32,13 +35,19 @@ class VisitCountRuleAdminInline(admin.TabularInline):
|
|||||||
administration interface for segments.
|
administration interface for segments.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = rules.VisitCountRule
|
model = rules.VisitCountRule
|
||||||
|
|
||||||
|
|
||||||
class SegmentAdmin(admin.ModelAdmin):
|
class SegmentAdmin(admin.ModelAdmin):
|
||||||
"""Add the inline models to the Segment admin interface."""
|
"""Add the inline models to the Segment admin interface."""
|
||||||
inlines = (UserIsLoggedInRuleAdminInline, TimeRuleAdminInline,
|
|
||||||
ReferralRuleAdminInline, VisitCountRuleAdminInline)
|
inlines = (
|
||||||
|
UserIsLoggedInRuleAdminInline,
|
||||||
|
TimeRuleAdminInline,
|
||||||
|
ReferralRuleAdminInline,
|
||||||
|
VisitCountRuleAdminInline,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.Segment, SegmentAdmin)
|
admin.site.register(models.Segment, SegmentAdmin)
|
||||||
|
@ -1,16 +1,24 @@
|
|||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from wagtail_personalisation import views
|
from wagtail_personalisation import views
|
||||||
|
|
||||||
app_name = 'segment'
|
app_name = "segment"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^segment/(?P<segment_id>[0-9]+)/toggle/$',
|
re_path(r"^segment/(?P<segment_id>[0-9]+)/toggle/$", views.toggle, name="toggle"),
|
||||||
views.toggle, name='toggle'),
|
re_path(
|
||||||
url(r'^(?P<page_id>[0-9]+)/copy/(?P<segment_id>[0-9]+)$',
|
r"^(?P<page_id>[0-9]+)/copy/(?P<segment_id>[0-9]+)$",
|
||||||
views.copy_page_view, name='copy_page'),
|
views.copy_page_view,
|
||||||
url(r'^segment/toggle_segment_view/$',
|
name="copy_page",
|
||||||
views.toggle_segment_view, name='toggle_segment_view'),
|
),
|
||||||
url(r'^segment/users/(?P<segment_id>[0-9]+)$',
|
re_path(
|
||||||
views.segment_user_data, name='segment_user_data'),
|
r"^segment/toggle_segment_view/$",
|
||||||
|
views.toggle_segment_view,
|
||||||
|
name="toggle_segment_view",
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
r"^segment/users/(?P<segment_id>[0-9]+)$",
|
||||||
|
views.segment_user_data,
|
||||||
|
name="segment_user_data",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django import VERSION as DJANGO_VERSION
|
||||||
from wagtail.core import blocks
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail import blocks
|
||||||
|
else:
|
||||||
|
from wagtail.core import blocks
|
||||||
|
|
||||||
from wagtail_personalisation.adapters import get_segment_adapter
|
from wagtail_personalisation.adapters import get_segment_adapter
|
||||||
from wagtail_personalisation.models import Segment
|
from wagtail_personalisation.models import Segment
|
||||||
@ -7,7 +18,7 @@ from wagtail_personalisation.models import Segment
|
|||||||
|
|
||||||
def list_segment_choices():
|
def list_segment_choices():
|
||||||
yield -1, ("Show to everyone")
|
yield -1, ("Show to everyone")
|
||||||
for pk, name in Segment.objects.values_list('pk', 'name'):
|
for pk, name in Segment.objects.values_list("pk", "name"):
|
||||||
yield pk, name
|
yield pk, name
|
||||||
|
|
||||||
|
|
||||||
@ -16,8 +27,10 @@ class PersonalisedStructBlock(blocks.StructBlock):
|
|||||||
|
|
||||||
segment = blocks.ChoiceBlock(
|
segment = blocks.ChoiceBlock(
|
||||||
choices=list_segment_choices,
|
choices=list_segment_choices,
|
||||||
required=False, label=_("Personalisation segment"),
|
required=False,
|
||||||
help_text=_("Only show this content block for users in this segment"))
|
label=_("Personalisation segment"),
|
||||||
|
help_text=_("Only show this content block for users in this segment"),
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, value, context=None):
|
def render(self, value, context=None):
|
||||||
"""Only render this content block for users in this segment.
|
"""Only render this content block for users in this segment.
|
||||||
@ -30,23 +43,21 @@ class PersonalisedStructBlock(blocks.StructBlock):
|
|||||||
:rtype: blocks.StructBlock or empty str
|
:rtype: blocks.StructBlock or empty str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
request = context['request']
|
request = context["request"]
|
||||||
adapter = get_segment_adapter(request)
|
adapter = get_segment_adapter(request)
|
||||||
user_segments = adapter.get_segments()
|
user_segments = adapter.get_segments()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
segment_id = int(value['segment'])
|
segment_id = int(value["segment"])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return ''
|
return ""
|
||||||
|
|
||||||
if segment_id > 0:
|
if segment_id > 0:
|
||||||
for segment in user_segments:
|
for segment in user_segments:
|
||||||
if segment.id == segment_id:
|
if segment.id == segment_id:
|
||||||
return super(PersonalisedStructBlock, self).render(
|
return super(PersonalisedStructBlock, self).render(value, context)
|
||||||
value, context)
|
|
||||||
|
|
||||||
if segment_id == -1:
|
if segment_id == -1:
|
||||||
return super(PersonalisedStructBlock, self).render(
|
return super(PersonalisedStructBlock, self).render(value, context)
|
||||||
value, context)
|
|
||||||
|
|
||||||
return ''
|
return ""
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class WagtailPersonalisationConfig(AppConfig):
|
class WagtailPersonalisationConfig(AppConfig):
|
||||||
label = 'wagtail_personalisation'
|
label = "wagtail_personalisation"
|
||||||
name = 'wagtail_personalisation'
|
name = "wagtail_personalisation"
|
||||||
verbose_name = _('Wagtail Personalisation')
|
verbose_name = _("Wagtail Personalisation")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from wagtail_personalisation import receivers
|
from wagtail_personalisation import receivers
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
from datetime import datetime
|
|
||||||
import functools
|
import functools
|
||||||
|
from datetime import datetime
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from wagtail.admin.forms import WagtailAdminModelForm
|
from wagtail.admin.forms import WagtailAdminModelForm
|
||||||
|
|
||||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||||
@ -23,10 +29,8 @@ def user_from_data(user_id):
|
|||||||
|
|
||||||
|
|
||||||
class SegmentAdminForm(WagtailAdminModelForm):
|
class SegmentAdminForm(WagtailAdminModelForm):
|
||||||
|
|
||||||
def count_matching_users(self, rules, match_any):
|
def count_matching_users(self, rules, match_any):
|
||||||
""" Calculates how many users match the given static rules
|
"""Calculates how many users match the given static rules"""
|
||||||
"""
|
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
static_rules = [rule for rule in rules if rule.static]
|
static_rules = [rule for rule in rules if rule.static]
|
||||||
@ -51,18 +55,28 @@ class SegmentAdminForm(WagtailAdminModelForm):
|
|||||||
Segment = self._meta.model
|
Segment = self._meta.model
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
form.instance for formset in self.formsets.values()
|
form.instance
|
||||||
|
for formset in self.formsets.values()
|
||||||
for form in formset
|
for form in formset
|
||||||
if form not in formset.deleted_forms
|
if form not in formset.deleted_forms
|
||||||
]
|
]
|
||||||
consistent = rules and Segment.all_static(rules)
|
consistent = rules and Segment.all_static(rules)
|
||||||
|
|
||||||
if cleaned_data.get('type') == Segment.TYPE_STATIC and not cleaned_data.get('count') and not consistent:
|
if (
|
||||||
self.add_error('count', _('Static segments with non-static compatible rules must include a count.'))
|
cleaned_data.get("type") == Segment.TYPE_STATIC
|
||||||
|
and not cleaned_data.get("count")
|
||||||
|
and not consistent
|
||||||
|
):
|
||||||
|
self.add_error(
|
||||||
|
"count",
|
||||||
|
_(
|
||||||
|
"Static segments with non-static compatible rules must include a count."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if self.instance.id and self.instance.is_static:
|
if self.instance.id and self.instance.is_static:
|
||||||
if self.has_changed():
|
if self.has_changed():
|
||||||
self.add_error_to_fields(self, excluded=['name', 'enabled'])
|
self.add_error_to_fields(self, excluded=["name", "enabled"])
|
||||||
|
|
||||||
for formset in self.formsets.values():
|
for formset in self.formsets.values():
|
||||||
if formset.has_changed():
|
if formset.has_changed():
|
||||||
@ -75,7 +89,7 @@ class SegmentAdminForm(WagtailAdminModelForm):
|
|||||||
def add_error_to_fields(self, form, excluded=list()):
|
def add_error_to_fields(self, form, excluded=list()):
|
||||||
for field in form.changed_data:
|
for field in form.changed_data:
|
||||||
if field not in excluded:
|
if field not in excluded:
|
||||||
form.add_error(field, _('Cannot update a static segment'))
|
form.add_error(field, _("Cannot update a static segment"))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
is_new = not self.instance.id
|
is_new = not self.instance.id
|
||||||
@ -85,12 +99,14 @@ class SegmentAdminForm(WagtailAdminModelForm):
|
|||||||
|
|
||||||
if is_new and self.instance.is_static and not self.instance.all_rules_static:
|
if is_new and self.instance.is_static and not self.instance.all_rules_static:
|
||||||
rules = [
|
rules = [
|
||||||
form.instance for formset in self.formsets.values()
|
form.instance
|
||||||
|
for formset in self.formsets.values()
|
||||||
for form in formset
|
for form in formset
|
||||||
if form not in formset.deleted_forms
|
if form not in formset.deleted_forms
|
||||||
]
|
]
|
||||||
self.instance.matched_users_count = self.count_matching_users(
|
self.instance.matched_users_count = self.count_matching_users(
|
||||||
rules, self.instance.match_any)
|
rules, self.instance.match_any
|
||||||
|
)
|
||||||
self.instance.matched_count_updated_at = datetime.now()
|
self.instance.matched_count_updated_at = datetime.now()
|
||||||
|
|
||||||
instance = super(SegmentAdminForm, self).save(*args, **kwargs)
|
instance = super(SegmentAdminForm, self).save(*args, **kwargs)
|
||||||
@ -98,7 +114,7 @@ class SegmentAdminForm(WagtailAdminModelForm):
|
|||||||
if is_new and instance.is_static and instance.all_rules_static:
|
if is_new and instance.is_static and instance.all_rules_static:
|
||||||
from .adapters import get_segment_adapter
|
from .adapters import get_segment_adapter
|
||||||
|
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get("/")
|
||||||
request.session = SessionStore()
|
request.session = SessionStore()
|
||||||
adapter = get_segment_adapter(request)
|
adapter = get_segment_adapter(request)
|
||||||
|
|
||||||
@ -111,7 +127,9 @@ class SegmentAdminForm(WagtailAdminModelForm):
|
|||||||
matched_count = 0
|
matched_count = 0
|
||||||
for user in users.iterator():
|
for user in users.iterator():
|
||||||
request.user = user
|
request.user = user
|
||||||
passes = adapter._test_rules(instance.get_rules(), request, instance.match_any)
|
passes = adapter._test_rules(
|
||||||
|
instance.get_rules(), request, instance.match_any
|
||||||
|
)
|
||||||
if passes:
|
if passes:
|
||||||
matched_count += 1
|
matched_count += 1
|
||||||
if instance.count == 0 or len(users_to_add) < instance.count:
|
if instance.count == 0 or len(users_to_add) < instance.count:
|
||||||
@ -130,7 +148,5 @@ class SegmentAdminForm(WagtailAdminModelForm):
|
|||||||
@property
|
@property
|
||||||
def media(self):
|
def media(self):
|
||||||
media = super(SegmentAdminForm, self).media
|
media = super(SegmentAdminForm, self).media
|
||||||
media.add_js(
|
media.add_js([static("js/segment_form_control.js")])
|
||||||
[static('js/segment_form_control.js')]
|
|
||||||
)
|
|
||||||
return media
|
return media
|
||||||
|
@ -12,81 +12,189 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtailcore', '0001_initial'),
|
("wagtailcore", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PersonalisablePage',
|
name="PersonalisablePage",
|
||||||
fields=[
|
fields=[
|
||||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
|
(
|
||||||
('is_segmented', models.BooleanField(default=False)),
|
"page_ptr",
|
||||||
('canonical_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='variants', to='wagtail_personalisation.PersonalisablePage')),
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_segmented", models.BooleanField(default=False)),
|
||||||
|
(
|
||||||
|
"canonical_page",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="variants",
|
||||||
|
to="wagtail_personalisation.PersonalisablePage",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page',),
|
bases=("wagtailcore.page",),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ReferralRule',
|
name="ReferralRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('regex_string', models.TextField(verbose_name='Regex string to match the referer with')),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"regex_string",
|
||||||
|
models.TextField(
|
||||||
|
verbose_name="Regex string to match the referer with"
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Segment',
|
name="Segment",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('name', models.CharField(max_length=255)),
|
"id",
|
||||||
('create_date', models.DateTimeField(auto_now_add=True)),
|
models.AutoField(
|
||||||
('edit_date', models.DateTimeField(auto_now=True)),
|
auto_created=True,
|
||||||
('enable_date', models.DateTimeField(editable=False, null=True)),
|
primary_key=True,
|
||||||
('disable_date', models.DateTimeField(editable=False, null=True)),
|
serialize=False,
|
||||||
('visit_count', models.PositiveIntegerField(default=0, editable=False)),
|
verbose_name="ID",
|
||||||
('status', models.CharField(choices=[('enabled', 'Enabled'), ('disabled', 'Disabled')], default='enabled', max_length=20)),
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
("create_date", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("edit_date", models.DateTimeField(auto_now=True)),
|
||||||
|
("enable_date", models.DateTimeField(editable=False, null=True)),
|
||||||
|
("disable_date", models.DateTimeField(editable=False, null=True)),
|
||||||
|
("visit_count", models.PositiveIntegerField(default=0, editable=False)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("enabled", "Enabled"), ("disabled", "Disabled")],
|
||||||
|
default="enabled",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='TimeRule',
|
name="TimeRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('start_time', models.TimeField(verbose_name='Starting time')),
|
"id",
|
||||||
('end_time', models.TimeField(verbose_name='Ending time')),
|
models.AutoField(
|
||||||
('segment', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_timerule_related', related_query_name='wagtail_personalisation_timerules', to='wagtail_personalisation.Segment')),
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("start_time", models.TimeField(verbose_name="Starting time")),
|
||||||
|
("end_time", models.TimeField(verbose_name="Ending time")),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_timerule_related",
|
||||||
|
related_query_name="wagtail_personalisation_timerules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='VisitCountRule',
|
name="VisitCountRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('operator', models.CharField(choices=[('more_than', 'More than'), ('less_than', 'Less than'), ('equal_to', 'Equal to')], default='ht', max_length=20)),
|
"id",
|
||||||
('count', models.PositiveSmallIntegerField(default=0, null=True)),
|
models.AutoField(
|
||||||
('counted_page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Page')),
|
auto_created=True,
|
||||||
('segment', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_visitcountrule_related', related_query_name='wagtail_personalisation_visitcountrules', to='wagtail_personalisation.Segment')),
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"operator",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("more_than", "More than"),
|
||||||
|
("less_than", "Less than"),
|
||||||
|
("equal_to", "Equal to"),
|
||||||
|
],
|
||||||
|
default="ht",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("count", models.PositiveSmallIntegerField(default=0, null=True)),
|
||||||
|
(
|
||||||
|
"counted_page",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="+",
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_visitcountrule_related",
|
||||||
|
related_query_name="wagtail_personalisation_visitcountrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='referralrule',
|
model_name="referralrule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_referralrule_related', related_query_name='wagtail_personalisation_referralrules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_referralrule_related",
|
||||||
|
related_query_name="wagtail_personalisation_referralrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='personalisablepage',
|
model_name="personalisablepage",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='segments', to='wagtail_personalisation.Segment'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="segments",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,25 +10,57 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0001_initial'),
|
("wagtail_personalisation", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='QueryRule',
|
name="QueryRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('query_parameter', models.TextField(verbose_name='The query parameter to search for')),
|
"id",
|
||||||
('query_value', models.TextField(verbose_name='The value of the parameter to match')),
|
models.AutoField(
|
||||||
('segment', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_queryrule_related', related_query_name='wagtail_personalisation_queryrules', to='wagtail_personalisation.Segment')),
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"query_parameter",
|
||||||
|
models.TextField(verbose_name="The query parameter to search for"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"query_value",
|
||||||
|
models.TextField(
|
||||||
|
verbose_name="The value of the parameter to match"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_queryrule_related",
|
||||||
|
related_query_name="wagtail_personalisation_queryrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='visitcountrule',
|
model_name="visitcountrule",
|
||||||
name='operator',
|
name="operator",
|
||||||
field=models.CharField(choices=[('more_than', 'More than'), ('less_than', 'Less than'), ('equal_to', 'Equal to')], default='more_than', max_length=20),
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("more_than", "More than"),
|
||||||
|
("less_than", "Less than"),
|
||||||
|
("equal_to", "Equal to"),
|
||||||
|
],
|
||||||
|
default="more_than",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,28 +8,36 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0002_auto_20161205_1623'),
|
("wagtail_personalisation", "0002_auto_20161205_1623"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='queryrule',
|
model_name="queryrule",
|
||||||
name='query_parameter',
|
name="query_parameter",
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='queryrule',
|
model_name="queryrule",
|
||||||
name='query_value',
|
name="query_value",
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='queryrule',
|
model_name="queryrule",
|
||||||
name='parameter',
|
name="parameter",
|
||||||
field=models.SlugField(default='test', max_length=20, verbose_name='The query parameter to search for'),
|
field=models.SlugField(
|
||||||
|
default="test",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="The query parameter to search for",
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='queryrule',
|
model_name="queryrule",
|
||||||
name='value',
|
name="value",
|
||||||
field=models.SlugField(default='test', max_length=20, verbose_name='The value of the parameter to match'),
|
field=models.SlugField(
|
||||||
|
default="test",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="The value of the parameter to match",
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,13 +8,15 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0003_auto_20161206_1005'),
|
("wagtail_personalisation", "0003_auto_20161206_1005"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='persistent',
|
name="persistent",
|
||||||
field=models.BooleanField(default=False, help_text='Should the segment persist between visits?'),
|
field=models.BooleanField(
|
||||||
|
default=False, help_text="Should the segment persist between visits?"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,19 +10,35 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0004_segment_persistent'),
|
("wagtail_personalisation", "0004_segment_persistent"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserIsLoggedInRule',
|
name="UserIsLoggedInRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('is_logged_in', models.BooleanField(default=False)),
|
"id",
|
||||||
('segment', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_userisloggedinrule_related', related_query_name='wagtail_personalisation_userisloggedinrules', to='wagtail_personalisation.Segment')),
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_logged_in", models.BooleanField(default=False)),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_userisloggedinrule_related",
|
||||||
|
related_query_name="wagtail_personalisation_userisloggedinrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,13 +8,16 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0005_userisloggedinrule'),
|
("wagtail_personalisation", "0005_userisloggedinrule"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='match_any',
|
name="match_any",
|
||||||
field=models.BooleanField(default=False, help_text='Should the segment match all the rules or just one of them?'),
|
field=models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Should the segment match all the rules or just one of them?",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,25 +10,41 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0006_segment_match_any'),
|
("wagtail_personalisation", "0006_segment_match_any"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DayRule',
|
name="DayRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('mon', models.BooleanField(default=False, verbose_name='Monday')),
|
"id",
|
||||||
('tue', models.BooleanField(default=False, verbose_name='Tuesday')),
|
models.AutoField(
|
||||||
('wed', models.BooleanField(default=False, verbose_name='Wednesday')),
|
auto_created=True,
|
||||||
('thu', models.BooleanField(default=False, verbose_name='Thursday')),
|
primary_key=True,
|
||||||
('fri', models.BooleanField(default=False, verbose_name='Friday')),
|
serialize=False,
|
||||||
('sat', models.BooleanField(default=False, verbose_name='Saturday')),
|
verbose_name="ID",
|
||||||
('sun', models.BooleanField(default=False, verbose_name='Sunday')),
|
),
|
||||||
('segment', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_dayrule_related', related_query_name='wagtail_personalisation_dayrules', to='wagtail_personalisation.Segment')),
|
),
|
||||||
|
("mon", models.BooleanField(default=False, verbose_name="Monday")),
|
||||||
|
("tue", models.BooleanField(default=False, verbose_name="Tuesday")),
|
||||||
|
("wed", models.BooleanField(default=False, verbose_name="Wednesday")),
|
||||||
|
("thu", models.BooleanField(default=False, verbose_name="Thursday")),
|
||||||
|
("fri", models.BooleanField(default=False, verbose_name="Friday")),
|
||||||
|
("sat", models.BooleanField(default=False, verbose_name="Saturday")),
|
||||||
|
("sun", models.BooleanField(default=False, verbose_name="Sunday")),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_dayrule_related",
|
||||||
|
related_query_name="wagtail_personalisation_dayrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,21 +10,40 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0007_dayrule'),
|
("wagtail_personalisation", "0007_dayrule"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DeviceRule',
|
name="DeviceRule",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('mobile', models.BooleanField(default=False, verbose_name='Mobile phone')),
|
"id",
|
||||||
('tablet', models.BooleanField(default=False, verbose_name='Tablet')),
|
models.AutoField(
|
||||||
('desktop', models.BooleanField(default=False, verbose_name='Desktop')),
|
auto_created=True,
|
||||||
('segment', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_devicerule_related', related_query_name='wagtail_personalisation_devicerules', to='wagtail_personalisation.Segment')),
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mobile",
|
||||||
|
models.BooleanField(default=False, verbose_name="Mobile phone"),
|
||||||
|
),
|
||||||
|
("tablet", models.BooleanField(default=False, verbose_name="Tablet")),
|
||||||
|
("desktop", models.BooleanField(default=False, verbose_name="Desktop")),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_devicerule_related",
|
||||||
|
related_query_name="wagtail_personalisation_devicerules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,23 +8,23 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0008_devicerule'),
|
("wagtail_personalisation", "0008_devicerule"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='personalisablepage',
|
model_name="personalisablepage",
|
||||||
name='canonical_page',
|
name="canonical_page",
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='personalisablepage',
|
model_name="personalisablepage",
|
||||||
name='page_ptr',
|
name="page_ptr",
|
||||||
),
|
),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='personalisablepage',
|
model_name="personalisablepage",
|
||||||
name='segment',
|
name="segment",
|
||||||
),
|
),
|
||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name='PersonalisablePage',
|
name="PersonalisablePage",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,41 +8,43 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0009_auto_20170531_0428'),
|
("wagtail_personalisation", "0009_auto_20170531_0428"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='dayrule',
|
name="dayrule",
|
||||||
options={'verbose_name': 'Day Rule'},
|
options={"verbose_name": "Day Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='devicerule',
|
name="devicerule",
|
||||||
options={'verbose_name': 'Device Rule'},
|
options={"verbose_name": "Device Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='queryrule',
|
name="queryrule",
|
||||||
options={'verbose_name': 'Query Rule'},
|
options={"verbose_name": "Query Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='referralrule',
|
name="referralrule",
|
||||||
options={'verbose_name': 'Referral Rule'},
|
options={"verbose_name": "Referral Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='timerule',
|
name="timerule",
|
||||||
options={'verbose_name': 'Time Rule'},
|
options={"verbose_name": "Time Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='userisloggedinrule',
|
name="userisloggedinrule",
|
||||||
options={'verbose_name': 'Logged in Rule'},
|
options={"verbose_name": "Logged in Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='visitcountrule',
|
name="visitcountrule",
|
||||||
options={'verbose_name': 'Visit count Rule'},
|
options={"verbose_name": "Visit count Rule"},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='referralrule',
|
model_name="referralrule",
|
||||||
name='regex_string',
|
name="regex_string",
|
||||||
field=models.TextField(verbose_name='Regular expression to match the referrer'),
|
field=models.TextField(
|
||||||
|
verbose_name="Regular expression to match the referrer"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -9,22 +9,55 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtailcore', '0001_initial'),
|
("wagtailcore", "0001_initial"),
|
||||||
('wagtail_personalisation', '0010_auto_20170531_1101'),
|
("wagtail_personalisation", "0010_auto_20170531_1101"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PersonalisablePageMetadata',
|
name="PersonalisablePageMetadata",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('is_segmented', models.BooleanField(default=False)),
|
"id",
|
||||||
('canonical_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='personalisable_canonical_metadata', to='wagtailcore.Page')),
|
models.AutoField(
|
||||||
('segment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='page_metadata', to='wagtail_personalisation.Segment')),
|
auto_created=True,
|
||||||
('variant', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='_personalisable_page_metadata', to='wagtailcore.Page')),
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("is_segmented", models.BooleanField(default=False)),
|
||||||
|
(
|
||||||
|
"canonical_page",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="personalisable_canonical_metadata",
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"segment",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="page_metadata",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"variant",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="_personalisable_page_metadata",
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,12 +8,12 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0011_personalisablepagemetadata'),
|
("wagtail_personalisation", "0011_personalisablepagemetadata"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='personalisablepagemetadata',
|
model_name="personalisablepagemetadata",
|
||||||
name='is_segmented',
|
name="is_segmented",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,24 +8,35 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('sessions', '0001_initial'),
|
("sessions", "0001_initial"),
|
||||||
('wagtail_personalisation', '0012_remove_personalisablepagemetadata_is_segmented'),
|
(
|
||||||
|
"wagtail_personalisation",
|
||||||
|
"0012_remove_personalisablepagemetadata_is_segmented",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='count',
|
name="count",
|
||||||
field=models.PositiveSmallIntegerField(default=0, help_text='If this number is set for a static segment users will be added to the set until the number is reached. After this no more users will be added.'),
|
field=models.PositiveSmallIntegerField(
|
||||||
|
default=0,
|
||||||
|
help_text="If this number is set for a static segment users will be added to the set until the number is reached. After this no more users will be added.",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='sessions',
|
name="sessions",
|
||||||
field=models.ManyToManyField(to='sessions.Session'),
|
field=models.ManyToManyField(to="sessions.Session"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='type',
|
name="type",
|
||||||
field=models.CharField(choices=[('dynamic', 'Dynamic'), ('static', 'Static')], default='dynamic', help_text='\n </br></br><strong>Dynamic:</strong> Users in this segment will change\n as more or less meet the rules specified in the segment.\n </br><strong>Static:</strong> If the segment contains only static\n compatible rules the segment will contain the members that pass\n those rules when the segment is created. Mixed static segments or\n those containing entirely non static compatible rules will be\n populated using the count variable.\n ', max_length=20),
|
field=models.CharField(
|
||||||
|
choices=[("dynamic", "Dynamic"), ("static", "Static")],
|
||||||
|
default="dynamic",
|
||||||
|
help_text="\n </br></br><strong>Dynamic:</strong> Users in this segment will change\n as more or less meet the rules specified in the segment.\n </br><strong>Static:</strong> If the segment contains only static\n compatible rules the segment will contain the members that pass\n those rules when the segment is created. Mixed static segments or\n those containing entirely non static compatible rules will be\n populated using the count variable.\n ",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,17 +10,17 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('wagtail_personalisation', '0013_add_dynamic_static_to_segment'),
|
("wagtail_personalisation", "0013_add_dynamic_static_to_segment"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='sessions',
|
name="sessions",
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='static_users',
|
name="static_users",
|
||||||
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,18 +8,18 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0015_static_users'),
|
("wagtail_personalisation", "0015_static_users"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='matched_count_updated_at',
|
name="matched_count_updated_at",
|
||||||
field=models.DateTimeField(editable=False, null=True),
|
field=models.DateTimeField(editable=False, null=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='matched_users_count',
|
name="matched_users_count",
|
||||||
field=models.PositiveIntegerField(default=0, editable=False),
|
field=models.PositiveIntegerField(default=0, editable=False),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -9,13 +9,22 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0016_auto_20180125_0918'),
|
("wagtail_personalisation", "0016_auto_20180125_0918"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='randomisation_percent',
|
name="randomisation_percent",
|
||||||
field=models.PositiveSmallIntegerField(blank=True, default=None, help_text='If this number is set each user matching the rules will have this percentage chance of being placed in the segment.', null=True, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(0)]),
|
field=models.PositiveSmallIntegerField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="If this number is set each user matching the rules will have this percentage chance of being placed in the segment.",
|
||||||
|
null=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MaxValueValidator(100),
|
||||||
|
django.core.validators.MinValueValidator(0),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -10,13 +10,17 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('wagtail_personalisation', '0017_segment_randomisation_percent'),
|
("wagtail_personalisation", "0017_segment_randomisation_percent"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='segment',
|
model_name="segment",
|
||||||
name='excluded_users',
|
name="excluded_users",
|
||||||
field=models.ManyToManyField(help_text='Users that matched the rules but were excluded from the segment for some reason e.g. randomisation', related_name='excluded_segments', to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(
|
||||||
|
help_text="Users that matched the rules but were excluded from the segment for some reason e.g. randomisation",
|
||||||
|
related_name="excluded_segments",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -7,13 +7,19 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0018_segment_excluded_users'),
|
("wagtail_personalisation", "0018_segment_excluded_users"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='personalisablepagemetadata',
|
model_name="personalisablepagemetadata",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='page_metadata', to='wagtail_personalisation.Segment'),
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="page_metadata",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -8,43 +8,71 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0019_auto_20180526_1425'),
|
("wagtail_personalisation", "0019_auto_20180526_1425"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='dayrule',
|
model_name="dayrule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_dayrules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_dayrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='devicerule',
|
model_name="devicerule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_devicerules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_devicerules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='queryrule',
|
model_name="queryrule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_queryrules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_queryrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='referralrule',
|
model_name="referralrule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_referralrules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_referralrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='timerule',
|
model_name="timerule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_timerules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_timerules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='userisloggedinrule',
|
model_name="userisloggedinrule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_userisloggedinrules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_userisloggedinrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='visitcountrule',
|
model_name="visitcountrule",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='wagtail_personalisation_visitcountrules', to='wagtail_personalisation.Segment'),
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="wagtail_personalisation_visitcountrules",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -7,13 +7,18 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0020_rules_delete_relatedqueryname'),
|
("wagtail_personalisation", "0020_rules_delete_relatedqueryname"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='personalisablepagemetadata',
|
model_name="personalisablepagemetadata",
|
||||||
name='segment',
|
name="segment",
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='page_metadata', to='wagtail_personalisation.Segment'),
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="page_metadata",
|
||||||
|
to="wagtail_personalisation.Segment",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -7,13 +7,21 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0021_personalisablepagemetadata_segment_set_on_delete_protect'),
|
(
|
||||||
|
"wagtail_personalisation",
|
||||||
|
"0021_personalisablepagemetadata_segment_set_on_delete_protect",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='personalisablepagemetadata',
|
model_name="personalisablepagemetadata",
|
||||||
name='canonical_page',
|
name="canonical_page",
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='personalisable_canonical_metadata', to='wagtailcore.Page'),
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="personalisable_canonical_metadata",
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -7,13 +7,21 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtail_personalisation', '0022_personalisablepagemetadata_canonical_protect'),
|
(
|
||||||
|
"wagtail_personalisation",
|
||||||
|
"0022_personalisablepagemetadata_canonical_protect",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='personalisablepagemetadata',
|
model_name="personalisablepagemetadata",
|
||||||
name='variant',
|
name="variant",
|
||||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='_personalisable_page_metadata', to='wagtailcore.Page'),
|
field=models.OneToOneField(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="_personalisable_page_metadata",
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,157 @@
|
|||||||
|
# Generated by Django 4.0.5 on 2022-06-30 15:25
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import modelcluster.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("wagtail_personalisation", "0025_auto_20190822_0627"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dayrule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dayrule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="devicerule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="devicerule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="origincountryrule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="origincountryrule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="personalisablepagemetadata",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="queryrule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="queryrule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="referralrule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="referralrule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="segment",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="timerule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="timerule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userisloggedinrule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userisloggedinrule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="visitcountrule",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="visitcountrule",
|
||||||
|
name="segment",
|
||||||
|
field=modelcluster.fields.ParentalKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="%(app_label)s_%(class)ss",
|
||||||
|
to="wagtail_personalisation.segment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,86 @@
|
|||||||
|
# Generated by Django 4.0.6 on 2022-07-26 08:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"wagtail_personalisation",
|
||||||
|
"0026_alter_dayrule_id_alter_dayrule_segment_and_more",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dayrule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="devicerule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="origincountryrule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="personalisablepagemetadata",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="queryrule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="referralrule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="segment",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="timerule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userisloggedinrule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="visitcountrule",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1,6 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
import wagtail
|
import wagtail
|
||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
@ -8,11 +9,32 @@ from django.db import models, transaction
|
|||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from modelcluster.models import ClusterableModel
|
from modelcluster.models import ClusterableModel
|
||||||
from wagtail.admin.edit_handlers import (
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel)
|
|
||||||
from wagtail.core.models import Page
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.admin.panels import (
|
||||||
|
FieldPanel,
|
||||||
|
FieldRowPanel,
|
||||||
|
InlinePanel,
|
||||||
|
MultiFieldPanel,
|
||||||
|
)
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
else:
|
||||||
|
from wagtail.admin.edit_handlers import (
|
||||||
|
FieldPanel,
|
||||||
|
FieldRowPanel,
|
||||||
|
InlinePanel,
|
||||||
|
MultiFieldPanel,
|
||||||
|
)
|
||||||
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from wagtail_personalisation.rules import AbstractBaseRule
|
from wagtail_personalisation.rules import AbstractBaseRule
|
||||||
from wagtail_personalisation.utils import count_active_days
|
from wagtail_personalisation.utils import count_active_days
|
||||||
@ -22,7 +44,7 @@ from .forms import SegmentAdminForm
|
|||||||
|
|
||||||
class RulePanel(InlinePanel):
|
class RulePanel(InlinePanel):
|
||||||
def on_model_bound(self):
|
def on_model_bound(self):
|
||||||
self.relation_name = self.relation_name.replace('_related', 's')
|
self.relation_name = self.relation_name.replace("_related", "s")
|
||||||
self.db_field = self.model._meta.get_field(self.relation_name)
|
self.db_field = self.model._meta.get_field(self.relation_name)
|
||||||
manager = getattr(self.model, self.relation_name)
|
manager = getattr(self.model, self.relation_name)
|
||||||
self.related = manager.rel
|
self.related = manager.rel
|
||||||
@ -35,20 +57,21 @@ class SegmentQuerySet(models.QuerySet):
|
|||||||
|
|
||||||
class Segment(ClusterableModel):
|
class Segment(ClusterableModel):
|
||||||
"""The segment model."""
|
"""The segment model."""
|
||||||
STATUS_ENABLED = 'enabled'
|
|
||||||
STATUS_DISABLED = 'disabled'
|
STATUS_ENABLED = "enabled"
|
||||||
|
STATUS_DISABLED = "disabled"
|
||||||
|
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
(STATUS_ENABLED, _('Enabled')),
|
(STATUS_ENABLED, _("Enabled")),
|
||||||
(STATUS_DISABLED, _('Disabled')),
|
(STATUS_DISABLED, _("Disabled")),
|
||||||
)
|
)
|
||||||
|
|
||||||
TYPE_DYNAMIC = 'dynamic'
|
TYPE_DYNAMIC = "dynamic"
|
||||||
TYPE_STATIC = 'static'
|
TYPE_STATIC = "static"
|
||||||
|
|
||||||
TYPE_CHOICES = (
|
TYPE_CHOICES = (
|
||||||
(TYPE_DYNAMIC, _('Dynamic')),
|
(TYPE_DYNAMIC, _("Dynamic")),
|
||||||
(TYPE_STATIC, _('Static')),
|
(TYPE_STATIC, _("Static")),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
@ -58,18 +81,22 @@ class Segment(ClusterableModel):
|
|||||||
disable_date = models.DateTimeField(null=True, editable=False)
|
disable_date = models.DateTimeField(null=True, editable=False)
|
||||||
visit_count = models.PositiveIntegerField(default=0, editable=False)
|
visit_count = models.PositiveIntegerField(default=0, editable=False)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20, choices=STATUS_CHOICES, default=STATUS_ENABLED)
|
max_length=20, choices=STATUS_CHOICES, default=STATUS_ENABLED
|
||||||
|
)
|
||||||
persistent = models.BooleanField(
|
persistent = models.BooleanField(
|
||||||
default=False, help_text=_("Should the segment persist between visits?"))
|
default=False, help_text=_("Should the segment persist between visits?")
|
||||||
|
)
|
||||||
match_any = models.BooleanField(
|
match_any = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("Should the segment match all the rules or just one of them?")
|
help_text=_("Should the segment match all the rules or just one of them?"),
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=TYPE_CHOICES,
|
choices=TYPE_CHOICES,
|
||||||
default=TYPE_DYNAMIC,
|
default=TYPE_DYNAMIC,
|
||||||
help_text=mark_safe(_("""
|
help_text=mark_safe(
|
||||||
|
_(
|
||||||
|
"""
|
||||||
</br></br><strong>Dynamic:</strong> Users in this segment will change
|
</br></br><strong>Dynamic:</strong> Users in this segment will change
|
||||||
as more or less meet the rules specified in the segment.
|
as more or less meet the rules specified in the segment.
|
||||||
</br><strong>Static:</strong> If the segment contains only static
|
</br><strong>Static:</strong> If the segment contains only static
|
||||||
@ -77,37 +104,42 @@ class Segment(ClusterableModel):
|
|||||||
those rules when the segment is created. Mixed static segments or
|
those rules when the segment is created. Mixed static segments or
|
||||||
those containing entirely non static compatible rules will be
|
those containing entirely non static compatible rules will be
|
||||||
populated using the count variable.
|
populated using the count variable.
|
||||||
"""))
|
"""
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
count = models.PositiveSmallIntegerField(
|
count = models.PositiveSmallIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"If this number is set for a static segment users will be added to the "
|
"If this number is set for a static segment users will be added to the "
|
||||||
"set until the number is reached. After this no more users will be added."
|
"set until the number is reached. After this no more users will be added."
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
static_users = models.ManyToManyField(
|
static_users = models.ManyToManyField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
)
|
)
|
||||||
excluded_users = models.ManyToManyField(
|
excluded_users = models.ManyToManyField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
help_text=_("Users that matched the rules but were excluded from the "
|
help_text=_(
|
||||||
"segment for some reason e.g. randomisation"),
|
"Users that matched the rules but were excluded from the "
|
||||||
related_name="excluded_segments"
|
"segment for some reason e.g. randomisation"
|
||||||
|
),
|
||||||
|
related_name="excluded_segments",
|
||||||
)
|
)
|
||||||
|
|
||||||
matched_users_count = models.PositiveIntegerField(default=0, editable=False)
|
matched_users_count = models.PositiveIntegerField(default=0, editable=False)
|
||||||
matched_count_updated_at = models.DateTimeField(null=True, editable=False)
|
matched_count_updated_at = models.DateTimeField(null=True, editable=False)
|
||||||
|
|
||||||
randomisation_percent = models.PositiveSmallIntegerField(
|
randomisation_percent = models.PositiveSmallIntegerField(
|
||||||
null=True, blank=True, default=None,
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"If this number is set each user matching the rules will "
|
"If this number is set each user matching the rules will "
|
||||||
"have this percentage chance of being placed in the segment."
|
"have this percentage chance of being placed in the segment."
|
||||||
), validators=[
|
),
|
||||||
MaxValueValidator(100),
|
validators=[MaxValueValidator(100), MinValueValidator(0)],
|
||||||
MinValueValidator(0)
|
)
|
||||||
])
|
|
||||||
|
|
||||||
objects = SegmentQuerySet.as_manager()
|
objects = SegmentQuerySet.as_manager()
|
||||||
|
|
||||||
@ -115,26 +147,37 @@ class Segment(ClusterableModel):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Segment.panels = [
|
Segment.panels = [
|
||||||
MultiFieldPanel([
|
MultiFieldPanel(
|
||||||
FieldPanel('name', classname="title"),
|
[
|
||||||
FieldRowPanel([
|
FieldPanel("name", classname="title"),
|
||||||
FieldPanel('status'),
|
FieldRowPanel(
|
||||||
FieldPanel('persistent'),
|
[
|
||||||
]),
|
FieldPanel("status"),
|
||||||
FieldPanel('match_any'),
|
FieldPanel("persistent"),
|
||||||
FieldPanel('type', widget=forms.RadioSelect),
|
]
|
||||||
FieldPanel('count', classname='count_field'),
|
|
||||||
FieldPanel('randomisation_percent', classname='percent_field'),
|
|
||||||
], heading="Segment"),
|
|
||||||
MultiFieldPanel([
|
|
||||||
RulePanel(
|
|
||||||
"{}_related".format(rule_model._meta.db_table),
|
|
||||||
label='{}{}'.format(
|
|
||||||
rule_model._meta.verbose_name,
|
|
||||||
' ({})'.format(_('Static compatible')) if rule_model.static else ''
|
|
||||||
),
|
),
|
||||||
) for rule_model in AbstractBaseRule.__subclasses__()
|
FieldPanel("match_any"),
|
||||||
], heading=_("Rules")),
|
FieldPanel("type", widget=forms.RadioSelect),
|
||||||
|
FieldPanel("count", classname="count_field"),
|
||||||
|
FieldPanel("randomisation_percent", classname="percent_field"),
|
||||||
|
],
|
||||||
|
heading="Segment",
|
||||||
|
),
|
||||||
|
MultiFieldPanel(
|
||||||
|
[
|
||||||
|
RulePanel(
|
||||||
|
"{}_related".format(rule_model._meta.db_table),
|
||||||
|
label="{}{}".format(
|
||||||
|
rule_model._meta.verbose_name,
|
||||||
|
" ({})".format(_("Static compatible"))
|
||||||
|
if rule_model.static
|
||||||
|
else "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for rule_model in AbstractBaseRule.__subclasses__()
|
||||||
|
],
|
||||||
|
heading=_("Rules"),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
super(Segment, self).__init__(*args, **kwargs)
|
super(Segment, self).__init__(*args, **kwargs)
|
||||||
@ -179,20 +222,21 @@ class Segment(ClusterableModel):
|
|||||||
"""Retrieve all rules in the segment."""
|
"""Retrieve all rules in the segment."""
|
||||||
segment_rules = []
|
segment_rules = []
|
||||||
for rule_model in AbstractBaseRule.get_descendant_models():
|
for rule_model in AbstractBaseRule.get_descendant_models():
|
||||||
segment_rules.extend(
|
segment_rules.extend(rule_model._default_manager.filter(segment=self))
|
||||||
rule_model._default_manager.filter(segment=self))
|
|
||||||
|
|
||||||
return segment_rules
|
return segment_rules
|
||||||
|
|
||||||
def toggle(self, save=True):
|
def toggle(self, save=True):
|
||||||
self.status = (
|
self.status = (
|
||||||
self.STATUS_ENABLED if self.status == self.STATUS_DISABLED
|
self.STATUS_ENABLED
|
||||||
else self.STATUS_DISABLED)
|
if self.status == self.STATUS_DISABLED
|
||||||
|
else self.STATUS_DISABLED
|
||||||
|
)
|
||||||
if save:
|
if save:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def randomise_into_segment(self):
|
def randomise_into_segment(self):
|
||||||
""" Returns True if randomisation_percent is not set or it generates
|
"""Returns True if randomisation_percent is not set or it generates
|
||||||
a random number less than the randomisation_percent
|
a random number less than the randomisation_percent
|
||||||
This is so there is some randomisation in which users are added to the
|
This is so there is some randomisation in which users are added to the
|
||||||
segment
|
segment
|
||||||
@ -210,21 +254,24 @@ class PersonalisablePageMetadata(ClusterableModel):
|
|||||||
segments.
|
segments.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Canonical pages should not ever be deleted if they have variants
|
# Canonical pages should not ever be deleted if they have variants
|
||||||
# because the variants will be orphaned.
|
# because the variants will be orphaned.
|
||||||
canonical_page = models.ForeignKey(
|
canonical_page = models.ForeignKey(
|
||||||
Page, models.PROTECT, related_name='personalisable_canonical_metadata',
|
Page,
|
||||||
null=True
|
models.PROTECT,
|
||||||
|
related_name="personalisable_canonical_metadata",
|
||||||
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete metadata of the variant if its page gets deleted.
|
# Delete metadata of the variant if its page gets deleted.
|
||||||
variant = models.OneToOneField(
|
variant = models.OneToOneField(
|
||||||
Page, models.CASCADE, related_name='_personalisable_page_metadata',
|
Page, models.CASCADE, related_name="_personalisable_page_metadata", null=True
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
segment = models.ForeignKey(Segment, models.PROTECT, null=True,
|
segment = models.ForeignKey(
|
||||||
related_name='page_metadata')
|
Segment, models.PROTECT, null=True, related_name="page_metadata"
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def has_variants(self):
|
def has_variants(self):
|
||||||
@ -241,10 +288,12 @@ class PersonalisablePageMetadata(ClusterableModel):
|
|||||||
@cached_property
|
@cached_property
|
||||||
def variants_metadata(self):
|
def variants_metadata(self):
|
||||||
return (
|
return (
|
||||||
PersonalisablePageMetadata.objects
|
PersonalisablePageMetadata.objects.filter(
|
||||||
.filter(canonical_page_id=self.canonical_page_id)
|
canonical_page_id=self.canonical_page_id
|
||||||
|
)
|
||||||
.exclude(variant_id=self.variant_id)
|
.exclude(variant_id=self.variant_id)
|
||||||
.exclude(variant_id=self.canonical_page_id))
|
.exclude(variant_id=self.canonical_page_id)
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_canonical(self):
|
def is_canonical(self):
|
||||||
@ -265,33 +314,31 @@ class PersonalisablePageMetadata(ClusterableModel):
|
|||||||
slug = "{}-{}".format(page.slug, segment.encoded_name())
|
slug = "{}-{}".format(page.slug, segment.encoded_name())
|
||||||
title = "{} ({})".format(page.title, segment.name)
|
title = "{} ({})".format(page.title, segment.name)
|
||||||
update_attrs = {
|
update_attrs = {
|
||||||
'title': title,
|
"title": title,
|
||||||
'slug': slug,
|
"slug": slug,
|
||||||
'live': False,
|
"live": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
new_page = self.canonical_page.copy(
|
new_page = self.canonical_page.copy(
|
||||||
update_attrs=update_attrs, copy_revisions=False)
|
update_attrs=update_attrs, copy_revisions=False
|
||||||
|
)
|
||||||
|
|
||||||
PersonalisablePageMetadata.objects.create(
|
PersonalisablePageMetadata.objects.create(
|
||||||
canonical_page=page,
|
canonical_page=page, variant=new_page, segment=segment
|
||||||
variant=new_page,
|
)
|
||||||
segment=segment)
|
|
||||||
return new_page
|
return new_page
|
||||||
|
|
||||||
def metadata_for_segments(self, segments):
|
def metadata_for_segments(self, segments):
|
||||||
return (
|
return self.__class__.objects.filter(
|
||||||
self.__class__.objects
|
canonical_page_id=self.canonical_page_id, segment__in=segments
|
||||||
.filter(
|
)
|
||||||
canonical_page_id=self.canonical_page_id,
|
|
||||||
segment__in=segments))
|
|
||||||
|
|
||||||
def get_unused_segments(self):
|
def get_unused_segments(self):
|
||||||
if self.is_canonical:
|
if self.is_canonical:
|
||||||
return (
|
return Segment.objects.exclude(
|
||||||
Segment.objects
|
page_metadata__canonical_page_id=self.canonical_page_id
|
||||||
.exclude(page_metadata__canonical_page_id=self.canonical_page_id))
|
)
|
||||||
return Segment.objects.none()
|
return Segment.objects.none()
|
||||||
|
|
||||||
|
|
||||||
@ -307,7 +354,8 @@ class PersonalisablePageMixin:
|
|||||||
metadata = self._personalisable_page_metadata
|
metadata = self._personalisable_page_metadata
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
metadata = PersonalisablePageMetadata.objects.create(
|
metadata = PersonalisablePageMetadata.objects.create(
|
||||||
canonical_page=self, variant=self)
|
canonical_page=self, variant=self
|
||||||
|
)
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
def get_sitemap_urls(self, request=None):
|
def get_sitemap_urls(self, request=None):
|
||||||
|
@ -3,6 +3,7 @@ import re
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
import pycountry
|
import pycountry
|
||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
@ -11,11 +12,20 @@ from django.db import models
|
|||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from modelcluster.fields import ParentalKey
|
from modelcluster.fields import ParentalKey
|
||||||
from user_agents import parse
|
from user_agents import parse
|
||||||
from wagtail.admin.edit_handlers import (
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
FieldPanel, FieldRowPanel, PageChooserPanel)
|
|
||||||
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.admin.panels import FieldPanel, FieldRowPanel
|
||||||
|
else:
|
||||||
|
from wagtail.admin.edit_handlers import FieldPanel, FieldRowPanel, PageChooserPanel
|
||||||
|
|
||||||
from wagtail_personalisation.utils import get_client_ip
|
from wagtail_personalisation.utils import get_client_ip
|
||||||
|
|
||||||
@ -27,32 +37,35 @@ logger = logging.getLogger(__name__)
|
|||||||
def get_geoip_module():
|
def get_geoip_module():
|
||||||
try:
|
try:
|
||||||
from django.contrib.gis.geoip2 import GeoIP2
|
from django.contrib.gis.geoip2 import GeoIP2
|
||||||
|
|
||||||
return GeoIP2
|
return GeoIP2
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
'GeoIP module is disabled. To use GeoIP for the origin\n'
|
"GeoIP module is disabled. To use GeoIP for the origin\n"
|
||||||
'country personaliastion rule please set it up as per '
|
"country personaliastion rule please set it up as per "
|
||||||
'documentation:\n'
|
"documentation:\n"
|
||||||
'https://docs.djangoproject.com/en/stable/ref/contrib/gis/'
|
"https://docs.djangoproject.com/en/stable/ref/contrib/gis/"
|
||||||
'geoip2/.\n'
|
"geoip2/.\n"
|
||||||
'Wagtail-personalisation also works with Cloudflare and\n'
|
"Wagtail-personalisation also works with Cloudflare and\n"
|
||||||
'CloudFront country detection, so you should not see this\n'
|
"CloudFront country detection, so you should not see this\n"
|
||||||
'warning if you use one of those.')
|
"warning if you use one of those."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbstractBaseRule(models.Model):
|
class AbstractBaseRule(models.Model):
|
||||||
"""Base for creating rules to segment users with."""
|
"""Base for creating rules to segment users with."""
|
||||||
icon = 'fa-circle-o'
|
|
||||||
|
icon = "fa-circle-o"
|
||||||
static = False
|
static = False
|
||||||
|
|
||||||
segment = ParentalKey(
|
segment = ParentalKey(
|
||||||
'wagtail_personalisation.Segment',
|
"wagtail_personalisation.Segment",
|
||||||
related_name="%(app_label)s_%(class)ss",
|
related_name="%(app_label)s_%(class)ss",
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name = 'Abstract segmentation rule'
|
verbose_name = "Abstract segmentation rule"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self._meta.verbose_name)
|
return str(self._meta.verbose_name)
|
||||||
@ -74,16 +87,17 @@ class AbstractBaseRule(models.Model):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
description = {
|
description = {
|
||||||
'title': _('Abstract segmentation rule'),
|
"title": _("Abstract segmentation rule"),
|
||||||
'value': '',
|
"value": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
return description
|
return description
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_descendant_models(cls):
|
def get_descendant_models(cls):
|
||||||
return [model for model in apps.get_models()
|
return [
|
||||||
if issubclass(model, AbstractBaseRule)]
|
model for model in apps.get_models() if issubclass(model, AbstractBaseRule)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class TimeRule(AbstractBaseRule):
|
class TimeRule(AbstractBaseRule):
|
||||||
@ -93,30 +107,32 @@ class TimeRule(AbstractBaseRule):
|
|||||||
set start time and end time.
|
set start time and end time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-clock-o'
|
|
||||||
|
icon = "fa-clock-o"
|
||||||
|
|
||||||
start_time = models.TimeField(_("Starting time"))
|
start_time = models.TimeField(_("Starting time"))
|
||||||
end_time = models.TimeField(_("Ending time"))
|
end_time = models.TimeField(_("Ending time"))
|
||||||
|
|
||||||
panels = [
|
panels = [
|
||||||
FieldRowPanel([
|
FieldRowPanel(
|
||||||
FieldPanel('start_time'),
|
[
|
||||||
FieldPanel('end_time'),
|
FieldPanel("start_time"),
|
||||||
]),
|
FieldPanel("end_time"),
|
||||||
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Time Rule')
|
verbose_name = _("Time Rule")
|
||||||
|
|
||||||
def test_user(self, request=None):
|
def test_user(self, request=None):
|
||||||
return self.start_time <= timezone.now().time() <= self.end_time
|
return self.start_time <= timezone.now().time() <= self.end_time
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return {
|
return {
|
||||||
'title': _('These users visit between'),
|
"title": _("These users visit between"),
|
||||||
'value': _('{} and {}').format(
|
"value": _("{} and {}").format(
|
||||||
self.start_time.strftime("%H:%M"),
|
self.start_time.strftime("%H:%M"), self.end_time.strftime("%H:%M")
|
||||||
self.end_time.strftime("%H:%M")
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +144,8 @@ class DayRule(AbstractBaseRule):
|
|||||||
set in the rule.
|
set in the rule.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-calendar-check-o'
|
|
||||||
|
icon = "fa-calendar-check-o"
|
||||||
|
|
||||||
mon = models.BooleanField(_("Monday"), default=False)
|
mon = models.BooleanField(_("Monday"), default=False)
|
||||||
tue = models.BooleanField(_("Tuesday"), default=False)
|
tue = models.BooleanField(_("Tuesday"), default=False)
|
||||||
@ -139,34 +156,39 @@ class DayRule(AbstractBaseRule):
|
|||||||
sun = models.BooleanField(_("Sunday"), default=False)
|
sun = models.BooleanField(_("Sunday"), default=False)
|
||||||
|
|
||||||
panels = [
|
panels = [
|
||||||
FieldPanel('mon'),
|
FieldPanel("mon"),
|
||||||
FieldPanel('tue'),
|
FieldPanel("tue"),
|
||||||
FieldPanel('wed'),
|
FieldPanel("wed"),
|
||||||
FieldPanel('thu'),
|
FieldPanel("thu"),
|
||||||
FieldPanel('fri'),
|
FieldPanel("fri"),
|
||||||
FieldPanel('sat'),
|
FieldPanel("sat"),
|
||||||
FieldPanel('sun'),
|
FieldPanel("sun"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Day Rule')
|
verbose_name = _("Day Rule")
|
||||||
|
|
||||||
def test_user(self, request=None):
|
def test_user(self, request=None):
|
||||||
return [self.mon, self.tue, self.wed, self.thu,
|
return [self.mon, self.tue, self.wed, self.thu, self.fri, self.sat, self.sun][
|
||||||
self.fri, self.sat, self.sun][timezone.now().date().weekday()]
|
timezone.now().date().weekday()
|
||||||
|
]
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
days = (
|
days = (
|
||||||
('mon', self.mon), ('tue', self.tue), ('wed', self.wed),
|
("mon", self.mon),
|
||||||
('thu', self.thu), ('fri', self.fri), ('sat', self.sat),
|
("tue", self.tue),
|
||||||
('sun', self.sun),
|
("wed", self.wed),
|
||||||
|
("thu", self.thu),
|
||||||
|
("fri", self.fri),
|
||||||
|
("sat", self.sat),
|
||||||
|
("sun", self.sun),
|
||||||
)
|
)
|
||||||
|
|
||||||
chosen_days = [day_name for day_name, chosen in days if chosen]
|
chosen_days = [day_name for day_name, chosen in days if chosen]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'title': _('These users visit on'),
|
"title": _("These users visit on"),
|
||||||
'value': ", ".join([day for day in chosen_days]).title(),
|
"value": ", ".join([day for day in chosen_days]).title(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -177,32 +199,32 @@ class ReferralRule(AbstractBaseRule):
|
|||||||
the set regex test.
|
the set regex test.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-globe'
|
|
||||||
|
|
||||||
regex_string = models.TextField(
|
icon = "fa-globe"
|
||||||
_("Regular expression to match the referrer"))
|
|
||||||
|
regex_string = models.TextField(_("Regular expression to match the referrer"))
|
||||||
|
|
||||||
panels = [
|
panels = [
|
||||||
FieldPanel('regex_string'),
|
FieldPanel("regex_string"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Referral Rule')
|
verbose_name = _("Referral Rule")
|
||||||
|
|
||||||
def test_user(self, request):
|
def test_user(self, request):
|
||||||
pattern = re.compile(self.regex_string)
|
pattern = re.compile(self.regex_string)
|
||||||
|
|
||||||
if 'HTTP_REFERER' in request.META:
|
if "HTTP_REFERER" in request.META:
|
||||||
referer = request.META['HTTP_REFERER']
|
referer = request.META["HTTP_REFERER"]
|
||||||
if pattern.search(referer):
|
if pattern.search(referer):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return {
|
return {
|
||||||
'title': _('These visits originate from'),
|
"title": _("These visits originate from"),
|
||||||
'value': self.regex_string,
|
"value": self.regex_string,
|
||||||
'code': True
|
"code": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -214,48 +236,66 @@ class VisitCountRule(AbstractBaseRule):
|
|||||||
when visiting the set page.
|
when visiting the set page.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-calculator'
|
|
||||||
|
icon = "fa-calculator"
|
||||||
static = True
|
static = True
|
||||||
|
|
||||||
OPERATOR_CHOICES = (
|
OPERATOR_CHOICES = (
|
||||||
('more_than', _("More than")),
|
("more_than", _("More than")),
|
||||||
('less_than', _("Less than")),
|
("less_than", _("Less than")),
|
||||||
('equal_to', _("Equal to")),
|
("equal_to", _("Equal to")),
|
||||||
|
)
|
||||||
|
operator = models.CharField(
|
||||||
|
max_length=20, choices=OPERATOR_CHOICES, default="more_than"
|
||||||
)
|
)
|
||||||
operator = models.CharField(max_length=20,
|
|
||||||
choices=OPERATOR_CHOICES, default="more_than")
|
|
||||||
count = models.PositiveSmallIntegerField(default=0, null=True)
|
count = models.PositiveSmallIntegerField(default=0, null=True)
|
||||||
counted_page = models.ForeignKey(
|
counted_page = models.ForeignKey(
|
||||||
'wagtailcore.Page',
|
"wagtailcore.Page",
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='+',
|
related_name="+",
|
||||||
)
|
)
|
||||||
|
|
||||||
panels = [
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
PageChooserPanel('counted_page'),
|
panels = [
|
||||||
FieldRowPanel([
|
FieldPanel("counted_page"),
|
||||||
FieldPanel('operator'),
|
FieldRowPanel(
|
||||||
FieldPanel('count'),
|
[
|
||||||
]),
|
FieldPanel("operator"),
|
||||||
]
|
FieldPanel("count"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
panels = [
|
||||||
|
PageChooserPanel("counted_page"),
|
||||||
|
FieldRowPanel(
|
||||||
|
[
|
||||||
|
FieldPanel("operator"),
|
||||||
|
FieldPanel("count"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Visit count Rule')
|
verbose_name = _("Visit count Rule")
|
||||||
|
|
||||||
def _get_user_session(self, user):
|
def _get_user_session(self, user):
|
||||||
sessions = Session.objects.iterator()
|
sessions = Session.objects.iterator()
|
||||||
for session in sessions:
|
for session in sessions:
|
||||||
session_data = session.get_decoded()
|
session_data = session.get_decoded()
|
||||||
if session_data.get('_auth_user_id') == str(user.id):
|
if session_data.get("_auth_user_id") == str(user.id):
|
||||||
return SessionStore(session_key=session.session_key)
|
return SessionStore(session_key=session.session_key)
|
||||||
return SessionStore()
|
return SessionStore()
|
||||||
|
|
||||||
def test_user(self, request, user=None):
|
def test_user(self, request, user=None):
|
||||||
# Local import for cyclic import
|
# Local import for cyclic import
|
||||||
from wagtail_personalisation.adapters import (
|
from wagtail_personalisation.adapters import (
|
||||||
get_segment_adapter, SessionSegmentsAdapter, SEGMENT_ADAPTER_CLASS)
|
SEGMENT_ADAPTER_CLASS,
|
||||||
|
SessionSegmentsAdapter,
|
||||||
|
get_segment_adapter,
|
||||||
|
)
|
||||||
|
|
||||||
# Django formsets don't honour 'required' fields so check rule is valid
|
# Django formsets don't honour 'required' fields so check rule is valid
|
||||||
try:
|
try:
|
||||||
@ -265,7 +305,7 @@ class VisitCountRule(AbstractBaseRule):
|
|||||||
|
|
||||||
if user:
|
if user:
|
||||||
# Create a fake request so we can use the adapter
|
# Create a fake request so we can use the adapter
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get("/")
|
||||||
request.user = user
|
request.user = user
|
||||||
|
|
||||||
# If we're using the session adapter check for an active session
|
# If we're using the session adapter check for an active session
|
||||||
@ -297,13 +337,8 @@ class VisitCountRule(AbstractBaseRule):
|
|||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return {
|
return {
|
||||||
'title': _('These users visited {}').format(
|
"title": _("These users visited {}").format(self.counted_page),
|
||||||
self.counted_page
|
"value": _("{} {} times").format(self.get_operator_display(), self.count),
|
||||||
),
|
|
||||||
'value': _('{} {} times').format(
|
|
||||||
self.get_operator_display(),
|
|
||||||
self.count
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_column_header(self):
|
def get_column_header(self):
|
||||||
@ -312,10 +347,13 @@ class VisitCountRule(AbstractBaseRule):
|
|||||||
def get_user_info_string(self, user):
|
def get_user_info_string(self, user):
|
||||||
# Local import for cyclic import
|
# Local import for cyclic import
|
||||||
from wagtail_personalisation.adapters import (
|
from wagtail_personalisation.adapters import (
|
||||||
get_segment_adapter, SessionSegmentsAdapter, SEGMENT_ADAPTER_CLASS)
|
SEGMENT_ADAPTER_CLASS,
|
||||||
|
SessionSegmentsAdapter,
|
||||||
|
get_segment_adapter,
|
||||||
|
)
|
||||||
|
|
||||||
# Create a fake request so we can use the adapter
|
# Create a fake request so we can use the adapter
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get("/")
|
||||||
request.user = user
|
request.user = user
|
||||||
|
|
||||||
# If we're using the session adapter check for an active session
|
# If we're using the session adapter check for an active session
|
||||||
@ -336,32 +374,28 @@ class QueryRule(AbstractBaseRule):
|
|||||||
present in the request query.
|
present in the request query.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-link'
|
|
||||||
|
|
||||||
parameter = models.SlugField(_("The query parameter to search for"),
|
icon = "fa-link"
|
||||||
max_length=20)
|
|
||||||
value = models.SlugField(_("The value of the parameter to match"),
|
parameter = models.SlugField(_("The query parameter to search for"), max_length=20)
|
||||||
max_length=20)
|
value = models.SlugField(_("The value of the parameter to match"), max_length=20)
|
||||||
|
|
||||||
panels = [
|
panels = [
|
||||||
FieldPanel('parameter'),
|
FieldPanel("parameter"),
|
||||||
FieldPanel('value'),
|
FieldPanel("value"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Query Rule')
|
verbose_name = _("Query Rule")
|
||||||
|
|
||||||
def test_user(self, request):
|
def test_user(self, request):
|
||||||
return request.GET.get(self.parameter, '') == self.value
|
return request.GET.get(self.parameter, "") == self.value
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return {
|
return {
|
||||||
'title': _('These users used a URL with the query'),
|
"title": _("These users used a URL with the query"),
|
||||||
'value': _('?{}={}').format(
|
"value": _("?{}={}").format(self.parameter, self.value),
|
||||||
self.parameter,
|
"code": True,
|
||||||
self.value
|
|
||||||
),
|
|
||||||
'code': True
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -372,23 +406,24 @@ class DeviceRule(AbstractBaseRule):
|
|||||||
in the request user agent headers.
|
in the request user agent headers.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-tablet'
|
|
||||||
|
icon = "fa-tablet"
|
||||||
|
|
||||||
mobile = models.BooleanField(_("Mobile phone"), default=False)
|
mobile = models.BooleanField(_("Mobile phone"), default=False)
|
||||||
tablet = models.BooleanField(_("Tablet"), default=False)
|
tablet = models.BooleanField(_("Tablet"), default=False)
|
||||||
desktop = models.BooleanField(_("Desktop"), default=False)
|
desktop = models.BooleanField(_("Desktop"), default=False)
|
||||||
|
|
||||||
panels = [
|
panels = [
|
||||||
FieldPanel('mobile'),
|
FieldPanel("mobile"),
|
||||||
FieldPanel('tablet'),
|
FieldPanel("tablet"),
|
||||||
FieldPanel('desktop'),
|
FieldPanel("desktop"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Device Rule')
|
verbose_name = _("Device Rule")
|
||||||
|
|
||||||
def test_user(self, request=None):
|
def test_user(self, request=None):
|
||||||
ua_header = request.META['HTTP_USER_AGENT']
|
ua_header = request.META["HTTP_USER_AGENT"]
|
||||||
user_agent = parse(ua_header)
|
user_agent = parse(ua_header)
|
||||||
|
|
||||||
if user_agent.is_mobile:
|
if user_agent.is_mobile:
|
||||||
@ -407,29 +442,31 @@ class UserIsLoggedInRule(AbstractBaseRule):
|
|||||||
Matches when the user is authenticated.
|
Matches when the user is authenticated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
icon = 'fa-user'
|
|
||||||
|
icon = "fa-user"
|
||||||
|
|
||||||
is_logged_in = models.BooleanField(default=False)
|
is_logged_in = models.BooleanField(default=False)
|
||||||
|
|
||||||
panels = [
|
panels = [
|
||||||
FieldPanel('is_logged_in'),
|
FieldPanel("is_logged_in"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Logged in Rule')
|
verbose_name = _("Logged in Rule")
|
||||||
|
|
||||||
def test_user(self, request=None):
|
def test_user(self, request=None):
|
||||||
return request.user.is_authenticated == self.is_logged_in
|
return request.user.is_authenticated == self.is_logged_in
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
return {
|
return {
|
||||||
'title': _('These visitors are'),
|
"title": _("These visitors are"),
|
||||||
'value': _('Logged in') if self.is_logged_in else _('Not logged in'),
|
"value": _("Logged in") if self.is_logged_in else _("Not logged in"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
COUNTRY_CHOICES = [(country.alpha_2.lower(), country.name)
|
COUNTRY_CHOICES = [
|
||||||
for country in pycountry.countries]
|
(country.alpha_2.lower(), country.name) for country in pycountry.countries
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class OriginCountryRule(AbstractBaseRule):
|
class OriginCountryRule(AbstractBaseRule):
|
||||||
@ -439,12 +476,16 @@ class OriginCountryRule(AbstractBaseRule):
|
|||||||
Using this rule requires setting up GeoIP2 on Django or using
|
Using this rule requires setting up GeoIP2 on Django or using
|
||||||
CloudFlare or CloudFront geolocation detection.
|
CloudFlare or CloudFront geolocation detection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
country = models.CharField(
|
country = models.CharField(
|
||||||
max_length=2, choices=COUNTRY_CHOICES,
|
max_length=2,
|
||||||
help_text=_("Select origin country of the request that this rule will "
|
choices=COUNTRY_CHOICES,
|
||||||
"match against. This rule will only work if you use "
|
help_text=_(
|
||||||
"Cloudflare or CloudFront IP geolocation or if GeoIP2 "
|
"Select origin country of the request that this rule will "
|
||||||
"module is configured.")
|
"match against. This rule will only work if you use "
|
||||||
|
"Cloudflare or CloudFront IP geolocation or if GeoIP2 "
|
||||||
|
"module is configured."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -458,13 +499,13 @@ class OriginCountryRule(AbstractBaseRule):
|
|||||||
https://support.cloudflare.com/hc/en-us/articles/200168236-What-does-Cloudflare-IP-Geolocation-do-
|
https://support.cloudflare.com/hc/en-us/articles/200168236-What-does-Cloudflare-IP-Geolocation-do-
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return request.META['HTTP_CF_IPCOUNTRY'].lower()
|
return request.META["HTTP_CF_IPCOUNTRY"].lower()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_cloudfront_country(self, request):
|
def get_cloudfront_country(self, request):
|
||||||
try:
|
try:
|
||||||
return request.META['HTTP_CLOUDFRONT_VIEWER_COUNTRY'].lower()
|
return request.META["HTTP_CLOUDFRONT_VIEWER_COUNTRY"].lower()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -487,4 +528,4 @@ class OriginCountryRule(AbstractBaseRule):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def test_user(self, request=None):
|
def test_user(self, request=None):
|
||||||
return (self.get_country(request) or '') == self.country.lower()
|
return (self.get_country(request) or "") == self.country.lower()
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
<li class="icon icon-fa-snowflake-o">
|
||||||
|
<a href="{{ target_url }}"><span>{{segment_count}}</span>{{title}}</a>
|
||||||
|
</li>
|
@ -5,6 +5,6 @@ from wagtail_personalisation.utils import count_active_days
|
|||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='days_since')
|
@register.filter(name="days_since")
|
||||||
def active_days(enable_date, disable_date):
|
def active_days(enable_date, disable_date):
|
||||||
return count_active_days(enable_date, disable_date)
|
return count_active_days(enable_date, disable_date)
|
||||||
|
@ -15,17 +15,17 @@ def do_segment(parser, token):
|
|||||||
tag_name, _, kwargs = parse_tag(token, parser)
|
tag_name, _, kwargs = parse_tag(token, parser)
|
||||||
|
|
||||||
# If no segment is provided this block will raise an error
|
# If no segment is provided this block will raise an error
|
||||||
if set(kwargs.keys()) != {'name'}:
|
if set(kwargs.keys()) != {"name"}:
|
||||||
usage = '{% segment name="segmentname" %} ... {% endsegment %}'
|
usage = '{% segment name="segmentname" %} ... {% endsegment %}'
|
||||||
raise TemplateSyntaxError("Usage: %s" % usage)
|
raise TemplateSyntaxError("Usage: %s" % usage)
|
||||||
|
|
||||||
nodelist = parser.parse(('endsegment',))
|
nodelist = parser.parse(("endsegment",))
|
||||||
parser.delete_first_token()
|
parser.delete_first_token()
|
||||||
|
|
||||||
return SegmentNode(nodelist, name=kwargs['name'])
|
return SegmentNode(nodelist, name=kwargs["name"])
|
||||||
|
|
||||||
|
|
||||||
register.tag('segment', do_segment)
|
register.tag("segment", do_segment)
|
||||||
|
|
||||||
|
|
||||||
class SegmentNode(template.Node):
|
class SegmentNode(template.Node):
|
||||||
@ -36,6 +36,7 @@ class SegmentNode(template.Node):
|
|||||||
If not it will return nothing
|
If not it will return nothing
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, nodelist, name):
|
def __init__(self, nodelist, name):
|
||||||
self.nodelist = nodelist
|
self.nodelist = nodelist
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -48,10 +49,10 @@ class SegmentNode(template.Node):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Check if user has segment
|
# Check if user has segment
|
||||||
adapter = get_segment_adapter(context['request'])
|
adapter = get_segment_adapter(context["request"])
|
||||||
user_segment = adapter.get_segment_by_id(segment_id=segment.pk)
|
user_segment = adapter.get_segment_by_id(segment_id=segment.pk)
|
||||||
if not user_segment:
|
if not user_segment:
|
||||||
return ''
|
return ""
|
||||||
|
|
||||||
content = self.nodelist.render(context)
|
content = self.nodelist.render(context)
|
||||||
content = mark_safe(content)
|
content = mark_safe(content)
|
||||||
|
@ -36,7 +36,7 @@ def create_segment_dictionary(segment):
|
|||||||
"encoded_name": segment.encoded_name(),
|
"encoded_name": segment.encoded_name(),
|
||||||
"id": segment.pk,
|
"id": segment.pk,
|
||||||
"timestamp": int(time.time()),
|
"timestamp": int(time.time()),
|
||||||
"persistent": segment.persistent
|
"persistent": segment.persistent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -107,9 +107,10 @@ def exclude_variants(pages):
|
|||||||
:rtype: QuerySet
|
:rtype: QuerySet
|
||||||
"""
|
"""
|
||||||
from wagtail_personalisation.models import PersonalisablePageMetadata
|
from wagtail_personalisation.models import PersonalisablePageMetadata
|
||||||
|
|
||||||
excluded_variant_pages = PersonalisablePageMetadata.objects.exclude(
|
excluded_variant_pages = PersonalisablePageMetadata.objects.exclude(
|
||||||
canonical_page_id=F('variant_id')
|
canonical_page_id=F("variant_id")
|
||||||
).values_list('variant_id')
|
).values_list("variant_id")
|
||||||
return pages.exclude(pk__in=excluded_variant_pages)
|
return pages.exclude(pk__in=excluded_variant_pages)
|
||||||
|
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ def get_client_ip(request):
|
|||||||
else:
|
else:
|
||||||
return func(request)
|
return func(request)
|
||||||
try:
|
try:
|
||||||
x_forwarded_for = request.META['HTTP_X_FORWARDED_FOR']
|
x_forwarded_for = request.META["HTTP_X_FORWARDED_FOR"]
|
||||||
return x_forwarded_for.split(',')[-1].strip()
|
return x_forwarded_for.split(",")[-1].strip()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return request.META['REMOTE_ADDR']
|
return request.META["REMOTE_ADDR"]
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
import csv
|
import csv
|
||||||
|
|
||||||
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import (
|
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
|
||||||
HttpResponse, HttpResponseForbidden, HttpResponseRedirect)
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
if DJANGO_VERSION >= (3, 0):
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
else:
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
|
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
|
||||||
from wagtail.contrib.modeladmin.views import DeleteView, IndexView
|
from wagtail.contrib.modeladmin.views import DeleteView, IndexView
|
||||||
from wagtail.core.models import Page
|
|
||||||
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.models import Page
|
||||||
|
else:
|
||||||
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from wagtail_personalisation.models import Segment
|
from wagtail_personalisation.models import Segment
|
||||||
from wagtail_personalisation.utils import can_delete_pages
|
from wagtail_personalisation.utils import can_delete_pages
|
||||||
@ -18,41 +28,42 @@ from wagtail_personalisation.utils import can_delete_pages
|
|||||||
|
|
||||||
class SegmentModelIndexView(IndexView):
|
class SegmentModelIndexView(IndexView):
|
||||||
"""Placeholder for additional list functionality."""
|
"""Placeholder for additional list functionality."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SegmentModelDashboardView(IndexView):
|
class SegmentModelDashboardView(IndexView):
|
||||||
"""Additional dashboard functionality."""
|
"""Additional dashboard functionality."""
|
||||||
|
|
||||||
def media(self):
|
def media(self):
|
||||||
return forms.Media(
|
return forms.Media(
|
||||||
css={'all': ['css/dashboard.css']},
|
css={"all": ["css/dashboard.css"]}, js=["js/commons.js", "js/dashboard.js"]
|
||||||
js=['js/commons.js', 'js/dashboard.js']
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
return [
|
return [
|
||||||
'modeladmin/wagtail_personalisation/segment/dashboard.html',
|
"modeladmin/wagtail_personalisation/segment/dashboard.html",
|
||||||
'modeladmin/index.html'
|
"modeladmin/index.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SegmentModelDeleteView(DeleteView):
|
class SegmentModelDeleteView(DeleteView):
|
||||||
def get_affected_page_objects(self):
|
def get_affected_page_objects(self):
|
||||||
return Page.objects.filter(pk__in=(
|
return Page.objects.filter(
|
||||||
self.instance.get_used_pages().values_list('variant_id', flat=True)
|
pk__in=(self.instance.get_used_pages().values_list("variant_id", flat=True))
|
||||||
))
|
)
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
return [
|
return [
|
||||||
'modeladmin/wagtail_personalisation/segment/delete.html',
|
"modeladmin/wagtail_personalisation/segment/delete.html",
|
||||||
'modeladmin/delete.html',
|
"modeladmin/delete.html",
|
||||||
]
|
]
|
||||||
|
|
||||||
def delete_instance(self):
|
def delete_instance(self):
|
||||||
page_variants = self.get_affected_page_objects()
|
page_variants = self.get_affected_page_objects()
|
||||||
if not can_delete_pages(page_variants, self.request.user):
|
if not can_delete_pages(page_variants, self.request.user):
|
||||||
raise PermissionDenied(
|
raise PermissionDenied(
|
||||||
'User has no permission to delete variant page objects.'
|
"User has no permission to delete variant page objects."
|
||||||
)
|
)
|
||||||
# Deleting page objects triggers deletion of the personalisation
|
# Deleting page objects triggers deletion of the personalisation
|
||||||
# metadata too because of models.CASCADE.
|
# metadata too because of models.CASCADE.
|
||||||
@ -63,8 +74,7 @@ class SegmentModelDeleteView(DeleteView):
|
|||||||
super().delete_instance()
|
super().delete_instance()
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not can_delete_pages(self.get_affected_page_objects(),
|
if not can_delete_pages(self.get_affected_page_objects(), self.request.user):
|
||||||
self.request.user):
|
|
||||||
context = self.get_context_data(
|
context = self.get_context_data(
|
||||||
cannot_delete_page_variants_error=True,
|
cannot_delete_page_variants_error=True,
|
||||||
)
|
)
|
||||||
@ -75,28 +85,39 @@ class SegmentModelDeleteView(DeleteView):
|
|||||||
@modeladmin_register
|
@modeladmin_register
|
||||||
class SegmentModelAdmin(ModelAdmin):
|
class SegmentModelAdmin(ModelAdmin):
|
||||||
"""The model admin for the Segments administration interface."""
|
"""The model admin for the Segments administration interface."""
|
||||||
|
|
||||||
model = Segment
|
model = Segment
|
||||||
index_view_class = SegmentModelIndexView
|
index_view_class = SegmentModelIndexView
|
||||||
dashboard_view_class = SegmentModelDashboardView
|
dashboard_view_class = SegmentModelDashboardView
|
||||||
delete_view_class = SegmentModelDeleteView
|
delete_view_class = SegmentModelDeleteView
|
||||||
menu_icon = 'fa-snowflake-o'
|
menu_icon = "fa-snowflake-o"
|
||||||
add_to_settings_menu = False
|
add_to_settings_menu = False
|
||||||
list_display = ('name', 'persistent', 'match_any', 'status',
|
list_display = (
|
||||||
'page_count', 'variant_count', 'statistics')
|
"name",
|
||||||
index_view_extra_js = ['js/commons.js', 'js/index.js']
|
"persistent",
|
||||||
index_view_extra_css = ['css/index.css']
|
"match_any",
|
||||||
form_view_extra_js = ['js/commons.js', 'js/form.js',
|
"status",
|
||||||
'js/segment_form_control.js',
|
"page_count",
|
||||||
'wagtailadmin/js/page-chooser-modal.js',
|
"variant_count",
|
||||||
'wagtailadmin/js/page-chooser.js']
|
"statistics",
|
||||||
form_view_extra_css = ['css/form.css']
|
)
|
||||||
|
index_view_extra_js = ["js/commons.js", "js/index.js"]
|
||||||
|
index_view_extra_css = ["css/index.css"]
|
||||||
|
form_view_extra_js = [
|
||||||
|
"js/commons.js",
|
||||||
|
"js/form.js",
|
||||||
|
"js/segment_form_control.js",
|
||||||
|
"wagtailadmin/js/page-chooser-modal.js",
|
||||||
|
"wagtailadmin/js/page-chooser.js",
|
||||||
|
]
|
||||||
|
form_view_extra_css = ["css/form.css"]
|
||||||
|
|
||||||
def index_view(self, request):
|
def index_view(self, request):
|
||||||
kwargs = {'model_admin': self}
|
kwargs = {"model_admin": self}
|
||||||
view_class = self.dashboard_view_class
|
view_class = self.dashboard_view_class
|
||||||
|
|
||||||
request.session.setdefault('segment_view', 'dashboard')
|
request.session.setdefault("segment_view", "dashboard")
|
||||||
if request.session['segment_view'] != 'dashboard':
|
if request.session["segment_view"] != "dashboard":
|
||||||
view_class = self.index_view_class
|
view_class = self.index_view_class
|
||||||
|
|
||||||
return view_class.as_view(**kwargs)(request)
|
return view_class.as_view(**kwargs)(request)
|
||||||
@ -109,7 +130,8 @@ class SegmentModelAdmin(ModelAdmin):
|
|||||||
|
|
||||||
def statistics(self, obj):
|
def statistics(self, obj):
|
||||||
return _("{visits} visits in {days} days").format(
|
return _("{visits} visits in {days} days").format(
|
||||||
visits=obj.visit_count, days=obj.get_active_days())
|
visits=obj.visit_count, days=obj.get_active_days()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def toggle_segment_view(request):
|
def toggle_segment_view(request):
|
||||||
@ -121,14 +143,14 @@ def toggle_segment_view(request):
|
|||||||
:rtype: django.http.HttpResponseRedirect
|
:rtype: django.http.HttpResponseRedirect
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if request.user.has_perm('wagtailadmin.access_admin'):
|
if request.user.has_perm("wagtailadmin.access_admin"):
|
||||||
if request.session['segment_view'] == 'dashboard':
|
if request.session["segment_view"] == "dashboard":
|
||||||
request.session['segment_view'] = 'list'
|
request.session["segment_view"] = "list"
|
||||||
|
|
||||||
elif request.session['segment_view'] != 'dashboard':
|
elif request.session["segment_view"] != "dashboard":
|
||||||
request.session['segment_view'] = 'dashboard'
|
request.session["segment_view"] = "dashboard"
|
||||||
|
|
||||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
|
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||||
|
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
@ -144,12 +166,12 @@ def toggle(request, segment_id):
|
|||||||
:rtype: django.http.HttpResponseRedirect
|
:rtype: django.http.HttpResponseRedirect
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if request.user.has_perm('wagtailadmin.access_admin'):
|
if request.user.has_perm("wagtailadmin.access_admin"):
|
||||||
segment = get_object_or_404(Segment, pk=segment_id)
|
segment = get_object_or_404(Segment, pk=segment_id)
|
||||||
|
|
||||||
segment.toggle()
|
segment.toggle()
|
||||||
|
|
||||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
|
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
|
||||||
|
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
@ -168,7 +190,7 @@ def copy_page_view(request, page_id, segment_id):
|
|||||||
:rtype: django.http.HttpResponseRedirect
|
:rtype: django.http.HttpResponseRedirect
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if request.user.has_perm('wagtailadmin.access_admin'):
|
if request.user.has_perm("wagtailadmin.access_admin"):
|
||||||
segment = get_object_or_404(Segment, pk=segment_id)
|
segment = get_object_or_404(Segment, pk=segment_id)
|
||||||
page = get_object_or_404(Page, pk=page_id).specific
|
page = get_object_or_404(Page, pk=page_id).specific
|
||||||
|
|
||||||
@ -178,7 +200,7 @@ def copy_page_view(request, page_id, segment_id):
|
|||||||
variant = variant_metadata.first()
|
variant = variant_metadata.first()
|
||||||
else:
|
else:
|
||||||
variant = metadata.copy_for_segment(segment)
|
variant = metadata.copy_for_segment(segment)
|
||||||
edit_url = reverse('wagtailadmin_pages:edit', args=[variant.id])
|
edit_url = reverse("wagtailadmin_pages:edit", args=[variant.id])
|
||||||
|
|
||||||
return HttpResponseRedirect(edit_url)
|
return HttpResponseRedirect(edit_url)
|
||||||
|
|
||||||
@ -187,14 +209,15 @@ def copy_page_view(request, page_id, segment_id):
|
|||||||
|
|
||||||
# CSV download views
|
# CSV download views
|
||||||
def segment_user_data(request, segment_id):
|
def segment_user_data(request, segment_id):
|
||||||
if request.user.has_perm('wagtailadmin.access_admin'):
|
if request.user.has_perm("wagtailadmin.access_admin"):
|
||||||
segment = get_object_or_404(Segment, pk=segment_id)
|
segment = get_object_or_404(Segment, pk=segment_id)
|
||||||
|
|
||||||
response = HttpResponse(content_type='text/csv; charset=utf-8')
|
response = HttpResponse(content_type="text/csv; charset=utf-8")
|
||||||
response['Content-Disposition'] = \
|
response[
|
||||||
'attachment;filename=segment-%s-users.csv' % str(segment_id)
|
"Content-Disposition"
|
||||||
|
] = "attachment;filename=segment-%s-users.csv" % str(segment_id)
|
||||||
|
|
||||||
headers = ['Username']
|
headers = ["Username"]
|
||||||
for rule in segment.get_rules():
|
for rule in segment.get_rules():
|
||||||
if rule.static:
|
if rule.static:
|
||||||
headers.append(rule.get_column_header())
|
headers.append(rule.get_column_header())
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from re import template
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django import VERSION as DJANGO_VERSION
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
from django.urls import reverse
|
from django.urls import include, re_path, reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from wagtail import VERSION as WAGTAIL_VERSION
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
@ -19,8 +20,13 @@ except ModuleNotFoundError:
|
|||||||
from wagtail.admin.views.pages import get_valid_next_url_from_request # noqa
|
from wagtail.admin.views.pages import get_valid_next_url_from_request # noqa
|
||||||
|
|
||||||
from wagtail.admin.widgets import Button, ButtonWithDropdownFromHook
|
from wagtail.admin.widgets import Button, ButtonWithDropdownFromHook
|
||||||
from wagtail.core import hooks
|
|
||||||
from wagtail.core.models import Page
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail import hooks
|
||||||
|
from wagtail.models import Page
|
||||||
|
else:
|
||||||
|
from wagtail.core import hooks
|
||||||
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from wagtail_personalisation import admin_urls, models, utils
|
from wagtail_personalisation import admin_urls, models, utils
|
||||||
from wagtail_personalisation.adapters import get_segment_adapter
|
from wagtail_personalisation.adapters import get_segment_adapter
|
||||||
@ -33,7 +39,7 @@ logger = logging.getLogger(__name__)
|
|||||||
def register_admin_urls():
|
def register_admin_urls():
|
||||||
"""Adds the administration urls for the personalisation apps."""
|
"""Adds the administration urls for the personalisation apps."""
|
||||||
return [
|
return [
|
||||||
url(
|
re_path(
|
||||||
r"^personalisation/",
|
r"^personalisation/",
|
||||||
include(admin_urls, namespace="wagtail_personalisation"),
|
include(admin_urls, namespace="wagtail_personalisation"),
|
||||||
)
|
)
|
||||||
@ -210,16 +216,10 @@ class CorrectedPagesSummaryItem(PagesSummaryItem):
|
|||||||
|
|
||||||
return page_count
|
return page_count
|
||||||
|
|
||||||
if WAGTAIL_VERSION >= (2, 15):
|
def get_context_data(self, parent_context):
|
||||||
def get_context_data(self, parent_context):
|
context = super().get_context_data(parent_context)
|
||||||
context = super().get_context_data(parent_context)
|
context["total_pages"] = self.get_total_pages(context)
|
||||||
context["total_pages"] = self.get_total_pages(context)
|
return context
|
||||||
return context
|
|
||||||
else:
|
|
||||||
def get_context(self):
|
|
||||||
context = super().get_context()
|
|
||||||
context["total_pages"] = self.get_total_pages(context)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
@hooks.register("construct_homepage_summary_items")
|
@hooks.register("construct_homepage_summary_items")
|
||||||
@ -236,20 +236,17 @@ class SegmentSummaryPanel(SummaryItem):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
template_name = "modeladmin/wagtail_personalisation/segment/summary.html"
|
||||||
order = 2000
|
order = 2000
|
||||||
|
|
||||||
def render(self):
|
def get_context_data(self, parent_context):
|
||||||
segment_count = models.Segment.objects.count()
|
context = super().get_context_data(parent_context)
|
||||||
target_url = reverse("wagtail_personalisation_segment_modeladmin_index")
|
context["segment_count"] = models.Segment.objects.count()
|
||||||
title = _("Segments")
|
context["target_url"] = reverse(
|
||||||
return mark_safe(
|
"wagtail_personalisation_segment_modeladmin_index"
|
||||||
"""
|
|
||||||
<li class="icon icon-fa-snowflake-o">
|
|
||||||
<a href="{}"><span>{}</span>{}</a>
|
|
||||||
</li>""".format(
|
|
||||||
target_url, segment_count, title
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
context["title"] = _("Segments")
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class PersonalisedPagesSummaryPanel(PagesSummaryItem):
|
class PersonalisedPagesSummaryPanel(PagesSummaryItem):
|
||||||
@ -304,7 +301,7 @@ def add_personalisation_summary_panels(request, items):
|
|||||||
def delete_related_variants(request, page):
|
def delete_related_variants(request, page):
|
||||||
if (
|
if (
|
||||||
not isinstance(page, models.PersonalisablePageMixin)
|
not isinstance(page, models.PersonalisablePageMixin)
|
||||||
or not page.personalisation_metadata.is_canonical
|
or not page.personalisation_metadata.is_canonical # noqa
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# Get a list of related personalisation metadata for all the related
|
# Get a list of related personalisation metadata for all the related
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
pytest_plugins = [
|
pytest_plugins = ["tests.fixtures"]
|
||||||
'tests.fixtures'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@ -10,7 +8,7 @@ def enable_db_access(db):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope="session")
|
||||||
def django_db_setup(django_db_setup, django_db_blocker):
|
def django_db_setup(django_db_setup, django_db_blocker):
|
||||||
from wagtail.core.models import Page, Site
|
from wagtail.core.models import Page, Site
|
||||||
|
|
||||||
|
@ -2,25 +2,31 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import factory
|
import factory
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
from wagtail_factories.factories import PageFactory
|
from wagtail_factories.factories import PageFactory
|
||||||
|
|
||||||
from tests.site.pages import models
|
from tests.site.pages import models
|
||||||
from wagtail_personalisation.models import PersonalisablePageMetadata
|
from wagtail_personalisation.models import PersonalisablePageMetadata
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from wagtail.core.models import Locale
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.models import Locale
|
||||||
|
else:
|
||||||
|
from wagtail.core.models import Locale
|
||||||
|
|
||||||
class LocaleFactory(factory.DjangoModelFactory):
|
class LocaleFactory(factory.django.DjangoModelFactory):
|
||||||
language_code = "en"
|
language_code = "en"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Locale
|
model = Locale
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ContentPageFactory(PageFactory):
|
class ContentPageFactory(PageFactory):
|
||||||
parent = None
|
parent = None
|
||||||
title = 'Test page'
|
title = "Test page"
|
||||||
slug = factory.LazyAttribute(lambda obj: slugify(obj.title))
|
slug = factory.LazyAttribute(lambda obj: slugify(obj.title))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -28,14 +34,13 @@ class ContentPageFactory(PageFactory):
|
|||||||
|
|
||||||
|
|
||||||
class RegularPageFactory(PageFactory):
|
class RegularPageFactory(PageFactory):
|
||||||
title = 'Regular page'
|
title = "Regular page"
|
||||||
slug = factory.LazyAttribute(lambda obj: slugify(obj.title))
|
slug = factory.LazyAttribute(lambda obj: slugify(obj.title))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.RegularPage
|
model = models.RegularPage
|
||||||
|
|
||||||
|
|
||||||
class PersonalisablePageMetadataFactory(factory.DjangoModelFactory):
|
class PersonalisablePageMetadataFactory(factory.django.DjangoModelFactory):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PersonalisablePageMetadata
|
model = PersonalisablePageMetadata
|
||||||
|
@ -7,32 +7,29 @@ import factory
|
|||||||
from wagtail_personalisation import rules
|
from wagtail_personalisation import rules
|
||||||
|
|
||||||
|
|
||||||
class DayRuleFactory(factory.DjangoModelFactory):
|
class DayRuleFactory(factory.django.DjangoModelFactory):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = rules.DayRule
|
model = rules.DayRule
|
||||||
|
|
||||||
|
|
||||||
class DeviceRuleFactory(factory.DjangoModelFactory):
|
class DeviceRuleFactory(factory.django.DjangoModelFactory):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = rules.DeviceRule
|
model = rules.DeviceRule
|
||||||
|
|
||||||
|
|
||||||
class QueryRuleFactory(factory.DjangoModelFactory):
|
class QueryRuleFactory(factory.django.DjangoModelFactory):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = rules.QueryRule
|
model = rules.QueryRule
|
||||||
|
|
||||||
|
|
||||||
class ReferralRuleFactory(factory.DjangoModelFactory):
|
class ReferralRuleFactory(factory.django.DjangoModelFactory):
|
||||||
regex_string = "test.test"
|
regex_string = "test.test"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = rules.ReferralRule
|
model = rules.ReferralRule
|
||||||
|
|
||||||
|
|
||||||
class TimeRuleFactory(factory.DjangoModelFactory):
|
class TimeRuleFactory(factory.django.DjangoModelFactory):
|
||||||
start_time = datetime.time(8, 0, 0)
|
start_time = datetime.time(8, 0, 0)
|
||||||
end_time = datetime.time(23, 0, 0)
|
end_time = datetime.time(23, 0, 0)
|
||||||
|
|
||||||
@ -40,7 +37,7 @@ class TimeRuleFactory(factory.DjangoModelFactory):
|
|||||||
model = rules.TimeRule
|
model = rules.TimeRule
|
||||||
|
|
||||||
|
|
||||||
class VisitCountRuleFactory(factory.DjangoModelFactory):
|
class VisitCountRuleFactory(factory.django.DjangoModelFactory):
|
||||||
operator = "more_than"
|
operator = "more_than"
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
@ -48,6 +45,6 @@ class VisitCountRuleFactory(factory.DjangoModelFactory):
|
|||||||
model = rules.VisitCountRule
|
model = rules.VisitCountRule
|
||||||
|
|
||||||
|
|
||||||
class OriginCountryRuleFactory(factory.DjangoModelFactory):
|
class OriginCountryRuleFactory(factory.django.DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = rules.OriginCountryRule
|
model = rules.OriginCountryRule
|
||||||
|
@ -5,8 +5,8 @@ import factory
|
|||||||
from wagtail_personalisation import models
|
from wagtail_personalisation import models
|
||||||
|
|
||||||
|
|
||||||
class SegmentFactory(factory.DjangoModelFactory):
|
class SegmentFactory(factory.django.DjangoModelFactory):
|
||||||
name = 'TestSegment'
|
name = "TestSegment"
|
||||||
status = models.Segment.STATUS_ENABLED
|
status = models.Segment.STATUS_ENABLED
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import factory
|
import factory
|
||||||
from wagtail.core.models import Site
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.models import Site
|
||||||
|
else:
|
||||||
|
from wagtail.core.models import Site
|
||||||
|
|
||||||
from tests.factories.page import ContentPageFactory
|
from tests.factories.page import ContentPageFactory
|
||||||
|
|
||||||
|
|
||||||
class SiteFactory(factory.DjangoModelFactory):
|
class SiteFactory(factory.django.DjangoModelFactory):
|
||||||
hostname = 'localhost'
|
hostname = "localhost"
|
||||||
port = factory.Sequence(lambda n: 81 + n)
|
port = factory.Sequence(lambda n: 81 + n)
|
||||||
site_name = 'Test site'
|
site_name = "Test site"
|
||||||
root_page = factory.SubFactory(ContentPageFactory, parent=None)
|
root_page = factory.SubFactory(ContentPageFactory, parent=None)
|
||||||
is_default_site = False
|
is_default_site = False
|
||||||
|
|
||||||
|
@ -9,23 +9,23 @@ from tests.factories.segment import SegmentFactory
|
|||||||
from tests.factories.site import SiteFactory
|
from tests.factories.site import SiteFactory
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope="function")
|
||||||
def site():
|
def site():
|
||||||
root_page = ContentPageFactory(parent=None, slug='')
|
root_page = ContentPageFactory(parent=None, slug="")
|
||||||
site = SiteFactory(is_default_site=True, root_page=root_page)
|
site = SiteFactory(is_default_site=True, root_page=root_page)
|
||||||
|
|
||||||
page1 = ContentPageFactory(parent=root_page, slug='page-1')
|
page1 = ContentPageFactory(parent=root_page, slug="page-1")
|
||||||
page2 = ContentPageFactory(parent=root_page, slug='page-2')
|
page2 = ContentPageFactory(parent=root_page, slug="page-2")
|
||||||
ContentPageFactory(parent=page1, slug='page-1-1')
|
ContentPageFactory(parent=page1, slug="page-1-1")
|
||||||
ContentPageFactory(parent=page2, slug='page-2-1')
|
ContentPageFactory(parent=page2, slug="page-2-1")
|
||||||
|
|
||||||
RegularPageFactory(parent=root_page, slug='regular')
|
RegularPageFactory(parent=root_page, slug="regular")
|
||||||
return site
|
return site
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def segmented_page(site):
|
def segmented_page(site):
|
||||||
page = ContentPageFactory(parent=site.root_page, slug='personalised')
|
page = ContentPageFactory(parent=site.root_page, slug="personalised")
|
||||||
segment = SegmentFactory()
|
segment = SegmentFactory()
|
||||||
return page.personalisation_metadata.copy_for_segment(segment)
|
return page.personalisation_metadata.copy_for_segment(segment)
|
||||||
|
|
||||||
@ -37,7 +37,6 @@ def rf():
|
|||||||
|
|
||||||
|
|
||||||
class RequestFactory(BaseRequestFactory):
|
class RequestFactory(BaseRequestFactory):
|
||||||
|
|
||||||
def request(self, user=None, **request):
|
def request(self, user=None, **request):
|
||||||
request = super(RequestFactory, self).request(**request)
|
request = super(RequestFactory, self).request(**request)
|
||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
@ -48,4 +47,4 @@ class RequestFactory(BaseRequestFactory):
|
|||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def user(django_user_model):
|
def user(django_user_model):
|
||||||
return django_user_model.objects.create(username='user')
|
return django_user_model.objects.create(username="user")
|
||||||
|
@ -1,98 +1,88 @@
|
|||||||
import os
|
import os
|
||||||
from importlib.util import find_spec
|
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': os.environ.get('DATABASE_ENGINE', 'django.db.backends.sqlite3'),
|
"ENGINE": os.environ.get("DATABASE_ENGINE", "django.db.backends.sqlite3"),
|
||||||
'NAME': os.environ.get('DATABASE_NAME', 'db.sqlite3'),
|
"NAME": os.environ.get("DATABASE_NAME", "db.sqlite3"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['localhost']
|
ALLOWED_HOSTS = ["localhost"]
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
"default": {
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
'LOCATION': 'unique-snowflake',
|
"LOCATION": "unique-snowflake",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECRET_KEY = 'not needed'
|
SECRET_KEY = "not needed"
|
||||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||||
|
|
||||||
ROOT_URLCONF = 'tests.site.urls'
|
ROOT_URLCONF = "tests.site.urls"
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
STATICFILES_FINDERS = (
|
STATICFILES_FINDERS = ("django.contrib.staticfiles.finders.AppDirectoriesFinder",)
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
|
||||||
)
|
|
||||||
|
|
||||||
USE_TZ = False
|
USE_TZ = False
|
||||||
|
|
||||||
TESTS_ROOT = os.path.dirname(os.path.abspath(__file__))
|
TESTS_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [
|
"DIRS": [
|
||||||
os.path.join(TESTS_ROOT, 'site', 'templates'),
|
os.path.join(TESTS_ROOT, "site", "templates"),
|
||||||
],
|
],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
],
|
],
|
||||||
'debug': True,
|
"debug": True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
'django.middleware.common.CommonMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
)
|
)
|
||||||
|
|
||||||
if find_spec('wagtail.contrib.legacy'):
|
|
||||||
MIDDLEWARE += ('wagtail.contrib.legacy.sitemiddleware.SiteMiddleware',)
|
|
||||||
else:
|
|
||||||
MIDDLEWARE += ('wagtail.core.middleware.SiteMiddleware', )
|
|
||||||
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'wagtail_personalisation',
|
"wagtail_personalisation",
|
||||||
|
"wagtail.contrib.modeladmin",
|
||||||
'wagtail.contrib.modeladmin',
|
"wagtail.search",
|
||||||
'wagtail.search',
|
"wagtail.sites",
|
||||||
'wagtail.sites',
|
"wagtail.users",
|
||||||
'wagtail.users',
|
"wagtail.images",
|
||||||
'wagtail.images',
|
"wagtail.documents",
|
||||||
'wagtail.documents',
|
"wagtail.admin",
|
||||||
'wagtail.admin',
|
"wagtail" if WAGTAIL_VERSION >= (3, 0) else "wagtail.core",
|
||||||
'wagtail.core',
|
"taggit",
|
||||||
|
"django.contrib.admin",
|
||||||
'taggit',
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.admin',
|
"django.contrib.sessions",
|
||||||
'django.contrib.auth',
|
"django.contrib.messages",
|
||||||
'django.contrib.contenttypes',
|
"django.contrib.staticfiles",
|
||||||
'django.contrib.sessions',
|
"tests.site.pages",
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
|
|
||||||
'tests.site.pages',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
PASSWORD_HASHERS = (
|
PASSWORD_HASHERS = (
|
||||||
'django.contrib.auth.hashers.MD5PasswordHasher', # don't use the intentionally slow default password hasher
|
"django.contrib.auth.hashers.MD5PasswordHasher", # don't use the intentionally slow default password hasher
|
||||||
)
|
)
|
||||||
|
|
||||||
WAGTAIL_SITE_NAME = 'wagtail-personalisation test'
|
WAGTAIL_SITE_NAME = "wagtail-personalisation test"
|
||||||
|
@ -14,20 +14,33 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtailcore', '0001_initial'),
|
("wagtailcore", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ContentPage',
|
name="ContentPage",
|
||||||
fields=[
|
fields=[
|
||||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), # noqa: E501
|
(
|
||||||
('subtitle', models.CharField(blank=True, default='', max_length=255)),
|
"page_ptr",
|
||||||
('body', wagtail.core.fields.RichTextField(blank=True, default='')),
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
), # noqa: E501
|
||||||
|
("subtitle", models.CharField(blank=True, default="", max_length=255)),
|
||||||
|
("body", wagtail.core.fields.RichTextField(blank=True, default="")),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
bases=(wagtail_personalisation.models.PersonalisablePageMixin, 'wagtailcore.page'),
|
bases=(
|
||||||
|
wagtail_personalisation.models.PersonalisablePageMixin,
|
||||||
|
"wagtailcore.page",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -9,21 +9,31 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('wagtailcore', '0001_initial'),
|
("wagtailcore", "0001_initial"),
|
||||||
('pages', '0001_initial'),
|
("pages", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='RegularPage',
|
name="RegularPage",
|
||||||
fields=[
|
fields=[
|
||||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), # noqa: E501
|
(
|
||||||
('subtitle', models.CharField(blank=True, default='', max_length=255)),
|
"page_ptr",
|
||||||
('body', wagtail.core.fields.RichTextField(blank=True, default='')),
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
), # noqa: E501
|
||||||
|
("subtitle", models.CharField(blank=True, default="", max_length=255)),
|
||||||
|
("body", wagtail.core.fields.RichTextField(blank=True, default="")),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
bases=('wagtailcore.page',),
|
bases=("wagtailcore.page",),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,26 +1,33 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from wagtail.admin.edit_handlers import FieldPanel
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
from wagtail.core.fields import RichTextField
|
|
||||||
from wagtail.core.models import Page
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
|
from wagtail.admin.panels import FieldPanel
|
||||||
|
from wagtail.fields import RichTextField
|
||||||
|
from wagtail.models import Page
|
||||||
|
else:
|
||||||
|
from wagtail.admin.edit_handlers import FieldPanel
|
||||||
|
from wagtail.core.fields import RichTextField
|
||||||
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from wagtail_personalisation.models import PersonalisablePageMixin
|
from wagtail_personalisation.models import PersonalisablePageMixin
|
||||||
|
|
||||||
|
|
||||||
class RegularPage(Page):
|
class RegularPage(Page):
|
||||||
subtitle = models.CharField(max_length=255, blank=True, default='')
|
subtitle = models.CharField(max_length=255, blank=True, default="")
|
||||||
body = RichTextField(blank=True, default='')
|
body = RichTextField(blank=True, default="")
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
FieldPanel('subtitle'),
|
FieldPanel("subtitle"),
|
||||||
FieldPanel('body'),
|
FieldPanel("body"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ContentPage(PersonalisablePageMixin, Page):
|
class ContentPage(PersonalisablePageMixin, Page):
|
||||||
subtitle = models.CharField(max_length=255, blank=True, default='')
|
subtitle = models.CharField(max_length=255, blank=True, default="")
|
||||||
body = RichTextField(blank=True, default='')
|
body = RichTextField(blank=True, default="")
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
FieldPanel('subtitle'),
|
FieldPanel("subtitle"),
|
||||||
FieldPanel('body'),
|
FieldPanel("body"),
|
||||||
]
|
]
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import include, re_path
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
from wagtail.core import urls as wagtail_urls
|
from wagtail.core import urls as wagtail_urls
|
||||||
from wagtail.documents import urls as wagtaildocs_urls
|
from wagtail.documents import urls as wagtaildocs_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^django-admin/', admin.site.urls),
|
re_path(r"^django-admin/", admin.site.urls),
|
||||||
|
re_path(r"^admin/", include(wagtailadmin_urls)),
|
||||||
url(r'^admin/', include(wagtailadmin_urls)),
|
re_path(r"^documents/", include(wagtaildocs_urls)),
|
||||||
url(r'^documents/', include(wagtaildocs_urls)),
|
re_path(r"", include(wagtail_urls)),
|
||||||
|
|
||||||
url(r'', include(wagtail_urls)),
|
|
||||||
]
|
]
|
||||||
|
@ -6,15 +6,15 @@ from wagtail_personalisation import adapters
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_segments(rf):
|
def test_get_segments(rf):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
|
|
||||||
segment_1 = SegmentFactory(name='segment-1', persistent=True)
|
segment_1 = SegmentFactory(name="segment-1", persistent=True)
|
||||||
segment_2 = SegmentFactory(name='segment-2', persistent=True)
|
segment_2 = SegmentFactory(name="segment-2", persistent=True)
|
||||||
|
|
||||||
adapter.set_segments([segment_1, segment_2])
|
adapter.set_segments([segment_1, segment_2])
|
||||||
assert len(request.session['segments']) == 2
|
assert len(request.session["segments"]) == 2
|
||||||
|
|
||||||
segments = adapter.get_segments()
|
segments = adapter.get_segments()
|
||||||
assert segments == [segment_1, segment_2]
|
assert segments == [segment_1, segment_2]
|
||||||
@ -22,15 +22,15 @@ def test_get_segments(rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_segments_session(rf):
|
def test_get_segments_session(rf):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
|
|
||||||
segment_1 = SegmentFactory(name='segment-1', persistent=True)
|
segment_1 = SegmentFactory(name="segment-1", persistent=True)
|
||||||
segment_2 = SegmentFactory(name='segment-2', persistent=True)
|
segment_2 = SegmentFactory(name="segment-2", persistent=True)
|
||||||
|
|
||||||
adapter.set_segments([segment_1, segment_2])
|
adapter.set_segments([segment_1, segment_2])
|
||||||
assert len(request.session['segments']) == 2
|
assert len(request.session["segments"]) == 2
|
||||||
|
|
||||||
adapter._segment_cache = None
|
adapter._segment_cache = None
|
||||||
segments = adapter.get_segments()
|
segments = adapter.get_segments()
|
||||||
@ -39,12 +39,12 @@ def test_get_segments_session(rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_segment_by_id(rf):
|
def test_get_segment_by_id(rf):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
|
|
||||||
segment_1 = SegmentFactory(name='segment-1', persistent=True)
|
segment_1 = SegmentFactory(name="segment-1", persistent=True)
|
||||||
segment_2 = SegmentFactory(name='segment-2', persistent=True)
|
segment_2 = SegmentFactory(name="segment-2", persistent=True)
|
||||||
|
|
||||||
adapter.set_segments([segment_1, segment_2])
|
adapter.set_segments([segment_1, segment_2])
|
||||||
|
|
||||||
@ -54,12 +54,12 @@ def test_get_segment_by_id(rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_refresh_removes_disabled(rf):
|
def test_refresh_removes_disabled(rf):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
|
|
||||||
segment_1 = SegmentFactory(name='segment-1', persistent=True)
|
segment_1 = SegmentFactory(name="segment-1", persistent=True)
|
||||||
segment_2 = SegmentFactory(name='segment-2', persistent=True)
|
segment_2 = SegmentFactory(name="segment-2", persistent=True)
|
||||||
|
|
||||||
adapter.set_segments([segment_1, segment_2])
|
adapter.set_segments([segment_1, segment_2])
|
||||||
|
|
||||||
@ -73,27 +73,27 @@ def test_refresh_removes_disabled(rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_add_page_visit(rf, site):
|
def test_add_page_visit(rf, site):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
adapter.add_page_visit(site.root_page)
|
adapter.add_page_visit(site.root_page)
|
||||||
|
|
||||||
assert request.session['visit_count'][0]['count'] == 1
|
assert request.session["visit_count"][0]["count"] == 1
|
||||||
|
|
||||||
adapter.add_page_visit(site.root_page)
|
adapter.add_page_visit(site.root_page)
|
||||||
assert request.session['visit_count'][0]['count'] == 2
|
assert request.session["visit_count"][0]["count"] == 2
|
||||||
|
|
||||||
assert adapter.get_visit_count() == 2
|
assert adapter.get_visit_count() == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_update_visit_count(rf, site):
|
def test_update_visit_count(rf, site):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
|
|
||||||
segment_1 = SegmentFactory(name='segment-1', persistent=True, visit_count=0)
|
segment_1 = SegmentFactory(name="segment-1", persistent=True, visit_count=0)
|
||||||
segment_2 = SegmentFactory(name='segment-2', persistent=True, visit_count=0)
|
segment_2 = SegmentFactory(name="segment-2", persistent=True, visit_count=0)
|
||||||
|
|
||||||
adapter.set_segments([segment_1, segment_2])
|
adapter.set_segments([segment_1, segment_2])
|
||||||
adapter.update_visit_count()
|
adapter.update_visit_count()
|
||||||
@ -107,12 +107,12 @@ def test_update_visit_count(rf, site):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_update_visit_count_deleted_segment(rf, site):
|
def test_update_visit_count_deleted_segment(rf, site):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
adapter = adapters.SessionSegmentsAdapter(request)
|
adapter = adapters.SessionSegmentsAdapter(request)
|
||||||
|
|
||||||
segment_1 = SegmentFactory(name='segment-1', persistent=True, visit_count=0)
|
segment_1 = SegmentFactory(name="segment-1", persistent=True, visit_count=0)
|
||||||
segment_2 = SegmentFactory(name='segment-2', persistent=True, visit_count=0)
|
segment_2 = SegmentFactory(name="segment-2", persistent=True, visit_count=0)
|
||||||
|
|
||||||
adapter.set_segments([segment_1, segment_2])
|
adapter.set_segments([segment_1, segment_2])
|
||||||
segment_2.delete()
|
segment_2.delete()
|
||||||
|
@ -15,11 +15,12 @@ from wagtail_personalisation.rules import TimeRule
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_segment_create():
|
def test_segment_create():
|
||||||
factoried_segment = SegmentFactory()
|
factoried_segment = SegmentFactory()
|
||||||
segment = Segment(name='TestSegment', status='enabled')
|
segment = Segment(name="TestSegment", status="enabled")
|
||||||
TimeRule(
|
TimeRule(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=segment)
|
segment=segment,
|
||||||
|
)
|
||||||
|
|
||||||
assert factoried_segment.name == segment.name
|
assert factoried_segment.name == segment.name
|
||||||
assert factoried_segment.status == segment.status
|
assert factoried_segment.status == segment.status
|
||||||
@ -27,21 +28,16 @@ def test_segment_create():
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_referral_rule_create():
|
def test_referral_rule_create():
|
||||||
segment = SegmentFactory(name='Referral')
|
segment = SegmentFactory(name="Referral")
|
||||||
referral_rule = ReferralRuleFactory(
|
referral_rule = ReferralRuleFactory(regex_string="test.test", segment=segment)
|
||||||
regex_string='test.test',
|
|
||||||
segment=segment)
|
|
||||||
|
|
||||||
assert referral_rule.regex_string == 'test.test'
|
assert referral_rule.regex_string == "test.test"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_query_rule_create():
|
def test_query_rule_create():
|
||||||
segment = SegmentFactory(name='Query')
|
segment = SegmentFactory(name="Query")
|
||||||
query_rule = QueryRuleFactory(
|
query_rule = QueryRuleFactory(parameter="query", value="value", segment=segment)
|
||||||
parameter="query",
|
|
||||||
value="value",
|
|
||||||
segment=segment)
|
|
||||||
|
|
||||||
assert query_rule.parameter == 'query'
|
assert query_rule.parameter == "query"
|
||||||
assert query_rule.value == 'value'
|
assert query_rule.value == "value"
|
||||||
|
@ -2,6 +2,7 @@ import datetime
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.db.models import ProtectedError
|
from django.db.models import ProtectedError
|
||||||
|
from wagtail import VERSION as WAGTAIL_VERSION
|
||||||
|
|
||||||
from tests.factories.page import ContentPageFactory
|
from tests.factories.page import ContentPageFactory
|
||||||
from tests.factories.segment import SegmentFactory
|
from tests.factories.segment import SegmentFactory
|
||||||
@ -16,7 +17,8 @@ def test_segment_create():
|
|||||||
TimeRule(
|
TimeRule(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=segment)
|
segment=segment,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@ -76,8 +78,8 @@ def test_sitemap_generation_for_variants_is_disabled(segmented_page):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_segment_edit_view(site, client, django_user_model):
|
def test_segment_edit_view(site, client, django_user_model):
|
||||||
test_segment = SegmentFactory()
|
test_segment = SegmentFactory()
|
||||||
try:
|
if WAGTAIL_VERSION >= (3, 0):
|
||||||
new_panel = test_segment.panels[1].children[0].bind_to(model=Segment)
|
|
||||||
except AttributeError:
|
|
||||||
new_panel = test_segment.panels[1].children[0].bind_to_model(Segment)
|
new_panel = test_segment.panels[1].children[0].bind_to_model(Segment)
|
||||||
|
else:
|
||||||
|
new_panel = test_segment.panels[1].children[0].bind_to(model=Segment)
|
||||||
assert new_panel.related.name == "wagtail_personalisation_timerules"
|
assert new_panel.related.name == "wagtail_personalisation_timerules"
|
||||||
|
@ -3,5 +3,5 @@ import pytest
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_device_segment_no_match(client, site):
|
def test_request_device_segment_no_match(client, site):
|
||||||
response = client.get('/regular/')
|
response = client.get("/regular/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -8,71 +8,72 @@ from tests.factories.segment import SegmentFactory
|
|||||||
from wagtail_personalisation.rules import get_geoip_module
|
from wagtail_personalisation.rules import get_geoip_module
|
||||||
|
|
||||||
skip_if_geoip2_installed = pytest.mark.skipif(
|
skip_if_geoip2_installed = pytest.mark.skipif(
|
||||||
find_spec('geoip2'), reason='requires GeoIP2 to be not installed'
|
find_spec("geoip2"), reason="requires GeoIP2 to be not installed"
|
||||||
)
|
)
|
||||||
|
|
||||||
skip_if_geoip2_not_installed = pytest.mark.skipif(
|
skip_if_geoip2_not_installed = pytest.mark.skipif(
|
||||||
not find_spec('geoip2'), reason='requires GeoIP2 to be installed.'
|
not find_spec("geoip2"), reason="requires GeoIP2 to be installed."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_cloudflare_country_with_header(rf):
|
def test_get_cloudflare_country_with_header(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/', HTTP_CF_IPCOUNTRY='PL')
|
request = rf.get("/", HTTP_CF_IPCOUNTRY="PL")
|
||||||
assert rule.get_cloudflare_country(request) == 'pl'
|
assert rule.get_cloudflare_country(request) == "pl"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_cloudflare_country_with_no_header(rf):
|
def test_get_cloudflare_country_with_no_header(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
assert 'HTTP_CF_IPCOUNTRY' not in request.META
|
assert "HTTP_CF_IPCOUNTRY" not in request.META
|
||||||
assert rule.get_cloudflare_country(request) is None
|
assert rule.get_cloudflare_country(request) is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_cloudfront_country_with_header(rf):
|
def test_get_cloudfront_country_with_header(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/', HTTP_CLOUDFRONT_VIEWER_COUNTRY='BY')
|
request = rf.get("/", HTTP_CLOUDFRONT_VIEWER_COUNTRY="BY")
|
||||||
assert rule.get_cloudfront_country(request) == 'by'
|
assert rule.get_cloudfront_country(request) == "by"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_cloudfront_country_with_no_header(rf):
|
def test_get_cloudfront_country_with_no_header(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
assert 'HTTP_CLOUDFRONT_VIEWER_COUNTRY' not in request.META
|
assert "HTTP_CLOUDFRONT_VIEWER_COUNTRY" not in request.META
|
||||||
assert rule.get_cloudfront_country(request) is None
|
assert rule.get_cloudfront_country(request) is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_geoip_country_with_remote_addr(rf):
|
def test_get_geoip_country_with_remote_addr(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/', REMOTE_ADDR='173.254.89.34')
|
request = rf.get("/", REMOTE_ADDR="173.254.89.34")
|
||||||
geoip_mock = MagicMock()
|
geoip_mock = MagicMock()
|
||||||
with patch('wagtail_personalisation.rules.get_geoip_module',
|
with patch(
|
||||||
return_value=geoip_mock) as geoip_import_mock:
|
"wagtail_personalisation.rules.get_geoip_module", return_value=geoip_mock
|
||||||
|
) as geoip_import_mock:
|
||||||
rule.get_geoip_country(request)
|
rule.get_geoip_country(request)
|
||||||
geoip_import_mock.assert_called_once()
|
geoip_import_mock.assert_called_once()
|
||||||
geoip_mock.assert_called_once()
|
geoip_mock.assert_called_once()
|
||||||
assert geoip_mock.mock_calls[1] == call().country_code('173.254.89.34')
|
assert geoip_mock.mock_calls[1] == call().country_code("173.254.89.34")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_country_calls_all_methods(rf):
|
def test_get_country_calls_all_methods(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
@patch.object(rule, 'get_geoip_country', return_value='')
|
@patch.object(rule, "get_geoip_country", return_value="")
|
||||||
@patch.object(rule, 'get_cloudflare_country', return_value='')
|
@patch.object(rule, "get_cloudflare_country", return_value="")
|
||||||
@patch.object(rule, 'get_cloudfront_country', return_value='')
|
@patch.object(rule, "get_cloudfront_country", return_value="")
|
||||||
def test_mock(cloudfront_mock, cloudflare_mock, geoip_mock):
|
def test_mock(cloudfront_mock, cloudflare_mock, geoip_mock):
|
||||||
country = rule.get_country(request)
|
country = rule.get_country(request)
|
||||||
cloudflare_mock.assert_called_once_with(request)
|
cloudflare_mock.assert_called_once_with(request)
|
||||||
@ -85,107 +86,106 @@ def test_get_country_calls_all_methods(rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_country_does_not_use_all_detection_methods_unnecessarily(rf):
|
def test_get_country_does_not_use_all_detection_methods_unnecessarily(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
@patch.object(rule, 'get_geoip_country', return_value='')
|
@patch.object(rule, "get_geoip_country", return_value="")
|
||||||
@patch.object(rule, 'get_cloudflare_country', return_value='t1')
|
@patch.object(rule, "get_cloudflare_country", return_value="t1")
|
||||||
@patch.object(rule, 'get_cloudfront_country', return_value='')
|
@patch.object(rule, "get_cloudfront_country", return_value="")
|
||||||
def test_mock(cloudfront_mock, cloudflare_mock, geoip_mock):
|
def test_mock(cloudfront_mock, cloudflare_mock, geoip_mock):
|
||||||
country = rule.get_country(request)
|
country = rule.get_country(request)
|
||||||
cloudflare_mock.assert_called_once_with(request)
|
cloudflare_mock.assert_called_once_with(request)
|
||||||
cloudfront_mock.assert_not_called()
|
cloudfront_mock.assert_not_called()
|
||||||
geoip_mock.assert_not_called()
|
geoip_mock.assert_not_called()
|
||||||
assert country == 't1'
|
assert country == "t1"
|
||||||
|
|
||||||
test_mock()
|
test_mock()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_calls_get_country(rf):
|
def test_test_user_calls_get_country(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
with patch.object(rule, 'get_country') as get_country_mock:
|
with patch.object(rule, "get_country") as get_country_mock:
|
||||||
rule.test_user(request)
|
rule.test_user(request)
|
||||||
get_country_mock.assert_called_once_with(request)
|
get_country_mock.assert_called_once_with(request)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_returns_true_if_cloudflare_country_match(rf):
|
def test_test_user_returns_true_if_cloudflare_country_match(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/', HTTP_CF_IPCOUNTRY='GB')
|
request = rf.get("/", HTTP_CF_IPCOUNTRY="GB")
|
||||||
assert rule.test_user(request) is True
|
assert rule.test_user(request) is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_returns_false_if_cloudflare_country_doesnt_match(rf):
|
def test_test_user_returns_false_if_cloudflare_country_doesnt_match(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/', HTTP_CF_IPCOUNTRY='NL')
|
request = rf.get("/", HTTP_CF_IPCOUNTRY="NL")
|
||||||
assert not rule.test_user(request)
|
assert not rule.test_user(request)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_returns_false_if_cloudfront_country_doesnt_match(rf):
|
def test_test_user_returns_false_if_cloudfront_country_doesnt_match(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='GB')
|
rule = OriginCountryRuleFactory(segment=segment, country="GB")
|
||||||
request = rf.get('/', HTTP_CLOUDFRONT_VIEWER_COUNTRY='NL')
|
request = rf.get("/", HTTP_CLOUDFRONT_VIEWER_COUNTRY="NL")
|
||||||
assert rule.test_user(request) is False
|
assert rule.test_user(request) is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_returns_true_if_cloudfront_country_matches(rf):
|
def test_test_user_returns_true_if_cloudfront_country_matches(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='se')
|
rule = OriginCountryRuleFactory(segment=segment, country="se")
|
||||||
request = rf.get('/', HTTP_CLOUDFRONT_VIEWER_COUNTRY='SE')
|
request = rf.get("/", HTTP_CLOUDFRONT_VIEWER_COUNTRY="SE")
|
||||||
assert rule.test_user(request) is True
|
assert rule.test_user(request) is True
|
||||||
|
|
||||||
|
|
||||||
@skip_if_geoip2_not_installed
|
@skip_if_geoip2_not_installed
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_geoip_module_matches(rf):
|
def test_test_user_geoip_module_matches(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='se')
|
rule = OriginCountryRuleFactory(segment=segment, country="se")
|
||||||
request = rf.get('/', REMOTE_ADDR='123.120.0.2')
|
request = rf.get("/", REMOTE_ADDR="123.120.0.2")
|
||||||
GeoIP2Mock = MagicMock()
|
GeoIP2Mock = MagicMock()
|
||||||
GeoIP2Mock().configure_mock(**{'country_code.return_value': 'SE'})
|
GeoIP2Mock().configure_mock(**{"country_code.return_value": "SE"})
|
||||||
GeoIP2Mock.reset_mock()
|
GeoIP2Mock.reset_mock()
|
||||||
with patch('wagtail_personalisation.rules.get_geoip_module',
|
with patch(
|
||||||
return_value=GeoIP2Mock):
|
"wagtail_personalisation.rules.get_geoip_module", return_value=GeoIP2Mock
|
||||||
|
):
|
||||||
assert rule.test_user(request) is True
|
assert rule.test_user(request) is True
|
||||||
assert GeoIP2Mock.mock_calls == [
|
assert GeoIP2Mock.mock_calls == [
|
||||||
call(),
|
call(),
|
||||||
call().country_code('123.120.0.2'),
|
call().country_code("123.120.0.2"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@skip_if_geoip2_not_installed
|
@skip_if_geoip2_not_installed
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_geoip_module_does_not_match(rf):
|
def test_test_user_geoip_module_does_not_match(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='nl')
|
rule = OriginCountryRuleFactory(segment=segment, country="nl")
|
||||||
request = rf.get('/', REMOTE_ADDR='123.120.0.2')
|
request = rf.get("/", REMOTE_ADDR="123.120.0.2")
|
||||||
GeoIP2Mock = MagicMock()
|
GeoIP2Mock = MagicMock()
|
||||||
GeoIP2Mock().configure_mock(**{'country_code.return_value': 'SE'})
|
GeoIP2Mock().configure_mock(**{"country_code.return_value": "SE"})
|
||||||
GeoIP2Mock.reset_mock()
|
GeoIP2Mock.reset_mock()
|
||||||
with patch('wagtail_personalisation.rules.get_geoip_module',
|
with patch(
|
||||||
return_value=GeoIP2Mock):
|
"wagtail_personalisation.rules.get_geoip_module", return_value=GeoIP2Mock
|
||||||
|
):
|
||||||
assert rule.test_user(request) is False
|
assert rule.test_user(request) is False
|
||||||
assert GeoIP2Mock.mock_calls == [
|
assert GeoIP2Mock.mock_calls == [call(), call().country_code("123.120.0.2")]
|
||||||
call(),
|
|
||||||
call().country_code('123.120.0.2')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@skip_if_geoip2_installed
|
@skip_if_geoip2_installed
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_test_user_does_not_use_geoip_module_if_disabled(rf):
|
def test_test_user_does_not_use_geoip_module_if_disabled(rf):
|
||||||
segment = SegmentFactory(name='Test segment')
|
segment = SegmentFactory(name="Test segment")
|
||||||
rule = OriginCountryRuleFactory(segment=segment, country='se')
|
rule = OriginCountryRuleFactory(segment=segment, country="se")
|
||||||
request = rf.get('/', REMOTE_ADDR='123.120.0.2')
|
request = rf.get("/", REMOTE_ADDR="123.120.0.2")
|
||||||
assert rule.test_user(request) is False
|
assert rule.test_user(request) is False
|
||||||
|
|
||||||
|
|
||||||
@ -199,4 +199,5 @@ def test_get_geoip_module_disabled():
|
|||||||
@skip_if_geoip2_not_installed
|
@skip_if_geoip2_not_installed
|
||||||
def test_get_geoip_module_enabled():
|
def test_get_geoip_module_enabled():
|
||||||
from django.contrib.gis.geoip2 import GeoIP2
|
from django.contrib.gis.geoip2 import GeoIP2
|
||||||
|
|
||||||
assert get_geoip_module() is GeoIP2
|
assert get_geoip_module() is GeoIP2
|
||||||
|
@ -9,7 +9,7 @@ from tests.factories.segment import SegmentFactory
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_day_rule_create():
|
def test_day_rule_create():
|
||||||
segment = SegmentFactory(name='DaySegment')
|
segment = SegmentFactory(name="DaySegment")
|
||||||
day_rule = DayRuleFactory(mon=True, thu=True, segment=segment)
|
day_rule = DayRuleFactory(mon=True, thu=True, segment=segment)
|
||||||
|
|
||||||
assert day_rule.mon is True
|
assert day_rule.mon is True
|
||||||
@ -20,11 +20,9 @@ def test_day_rule_create():
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@freeze_time("2017-01-01")
|
@freeze_time("2017-01-01")
|
||||||
def test_request_day_segment(client, site):
|
def test_request_day_segment(client, site):
|
||||||
day_only_segment = SegmentFactory(name='Day only')
|
day_only_segment = SegmentFactory(name="Day only")
|
||||||
DayRuleFactory(
|
DayRuleFactory(sun=True, segment=day_only_segment)
|
||||||
sun=True,
|
|
||||||
segment=day_only_segment)
|
|
||||||
|
|
||||||
client.get('/')
|
client.get("/")
|
||||||
|
|
||||||
assert client.session['segments'][0]['encoded_name'] == 'day-only'
|
assert client.session["segments"][0]["encoded_name"] == "day-only"
|
||||||
|
@ -6,7 +6,7 @@ from tests.factories.segment import SegmentFactory
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_device_rule_create():
|
def test_device_rule_create():
|
||||||
segment = SegmentFactory(name='DeviceSegment')
|
segment = SegmentFactory(name="DeviceSegment")
|
||||||
device_rule = DeviceRuleFactory(mobile=True, segment=segment)
|
device_rule = DeviceRuleFactory(mobile=True, segment=segment)
|
||||||
|
|
||||||
assert device_rule.mobile is True
|
assert device_rule.mobile is True
|
||||||
@ -16,23 +16,25 @@ def test_device_rule_create():
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_device_segment(client, site):
|
def test_request_device_segment(client, site):
|
||||||
device_only_segment = SegmentFactory(name='Device only')
|
device_only_segment = SegmentFactory(name="Device only")
|
||||||
DeviceRuleFactory(
|
DeviceRuleFactory(tablet=True, segment=device_only_segment)
|
||||||
tablet=True,
|
|
||||||
segment=device_only_segment)
|
|
||||||
|
|
||||||
client.get('/', **{'HTTP_USER_AGENT': 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X)'})
|
client.get(
|
||||||
|
"/",
|
||||||
|
**{"HTTP_USER_AGENT": "Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X)"}
|
||||||
|
)
|
||||||
|
|
||||||
assert client.session['segments'][0]['encoded_name'] == 'device-only'
|
assert client.session["segments"][0]["encoded_name"] == "device-only"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_device_segment_no_match(client, site):
|
def test_request_device_segment_no_match(client, site):
|
||||||
no_device_segment = SegmentFactory(name='No device')
|
no_device_segment = SegmentFactory(name="No device")
|
||||||
DeviceRuleFactory(
|
DeviceRuleFactory(mobile=True, segment=no_device_segment)
|
||||||
mobile=True,
|
|
||||||
segment=no_device_segment)
|
|
||||||
|
|
||||||
client.get('/', **{'HTTP_USER_AGENT': 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X)'})
|
client.get(
|
||||||
|
"/",
|
||||||
|
**{"HTTP_USER_AGENT": "Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X)"}
|
||||||
|
)
|
||||||
|
|
||||||
assert not client.session['segments']
|
assert not client.session["segments"]
|
||||||
|
@ -11,80 +11,68 @@ from tests.factories.segment import SegmentFactory
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_no_segments(client, site):
|
def test_no_segments(client, site):
|
||||||
response = client.get('/')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert client.session['segments'] == []
|
assert client.session["segments"] == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_referral_segment(client, site):
|
def test_referral_segment(client, site):
|
||||||
referral_segment = SegmentFactory(name='Referral')
|
referral_segment = SegmentFactory(name="Referral")
|
||||||
ReferralRuleFactory(
|
ReferralRuleFactory(regex_string="test.test", segment=referral_segment)
|
||||||
regex_string="test.test",
|
|
||||||
segment=referral_segment
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get('/', **{'HTTP_REFERER': 'test.test'})
|
response = client.get("/", **{"HTTP_REFERER": "test.test"})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert client.session['segments'][0]['encoded_name'] == 'referral'
|
assert client.session["segments"][0]["encoded_name"] == "referral"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@freeze_time("10:00:00")
|
@freeze_time("10:00:00")
|
||||||
def test_time_and_referral_segment(client, site):
|
def test_time_and_referral_segment(client, site):
|
||||||
segment = SegmentFactory(name='Both')
|
segment = SegmentFactory(name="Both")
|
||||||
TimeRuleFactory(
|
TimeRuleFactory(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=segment
|
segment=segment,
|
||||||
)
|
|
||||||
ReferralRuleFactory(
|
|
||||||
regex_string="test.test",
|
|
||||||
segment=segment
|
|
||||||
)
|
)
|
||||||
|
ReferralRuleFactory(regex_string="test.test", segment=segment)
|
||||||
|
|
||||||
response = client.get('/', **{'HTTP_REFERER': 'test.test'})
|
response = client.get("/", **{"HTTP_REFERER": "test.test"})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert client.session['segments'][0]['encoded_name'] == 'both'
|
assert client.session["segments"][0]["encoded_name"] == "both"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@freeze_time("7:00:00")
|
@freeze_time("7:00:00")
|
||||||
def test_no_time_but_referral_segment(client, site):
|
def test_no_time_but_referral_segment(client, site):
|
||||||
segment = SegmentFactory(name='Not both')
|
segment = SegmentFactory(name="Not both")
|
||||||
TimeRuleFactory(
|
TimeRuleFactory(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=segment
|
segment=segment,
|
||||||
)
|
|
||||||
ReferralRuleFactory(
|
|
||||||
regex_string="test.test",
|
|
||||||
segment=segment
|
|
||||||
)
|
)
|
||||||
|
ReferralRuleFactory(regex_string="test.test", segment=segment)
|
||||||
|
|
||||||
response = client.get('/', **{'HTTP_REFERER': 'test.test'})
|
response = client.get("/", **{"HTTP_REFERER": "test.test"})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@freeze_time("9:00:00")
|
@freeze_time("9:00:00")
|
||||||
def test_time_but_no_referral_segment(client, site):
|
def test_time_but_no_referral_segment(client, site):
|
||||||
segment = SegmentFactory(name='Not both')
|
segment = SegmentFactory(name="Not both")
|
||||||
TimeRuleFactory(
|
TimeRuleFactory(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=segment
|
segment=segment,
|
||||||
)
|
|
||||||
ReferralRuleFactory(
|
|
||||||
regex_string="test.test",
|
|
||||||
segment=segment
|
|
||||||
)
|
)
|
||||||
|
ReferralRuleFactory(regex_string="test.test", segment=segment)
|
||||||
|
|
||||||
response = client.get('/')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
|
@ -6,43 +6,33 @@ from tests.factories.segment import SegmentFactory
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_query_rule(client, site):
|
def test_request_query_rule(client, site):
|
||||||
segment = SegmentFactory(name='Query')
|
segment = SegmentFactory(name="Query")
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(
|
||||||
parameter="query",
|
parameter="query",
|
||||||
value="value",
|
value="value",
|
||||||
segment=segment,
|
segment=segment,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/?query=value')
|
response = client.get("/?query=value")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert any(
|
assert any(item["encoded_name"] == "query" for item in client.session["segments"])
|
||||||
item['encoded_name'] == 'query' for item in client.session['segments'])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_only_one_query_rule(client, site):
|
def test_request_only_one_query_rule(client, site):
|
||||||
segment = SegmentFactory(name='Query')
|
segment = SegmentFactory(name="Query")
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(parameter="query", value="value", segment=segment)
|
||||||
parameter="query",
|
|
||||||
value="value",
|
|
||||||
segment=segment
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get('/?test=test&query=value')
|
response = client.get("/?test=test&query=value")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert any(
|
assert any(item["encoded_name"] == "query" for item in client.session["segments"])
|
||||||
item['encoded_name'] == 'query' for item in client.session['segments'])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_multiple_queries(client, site):
|
def test_request_multiple_queries(client, site):
|
||||||
segment = SegmentFactory(name='Multiple queries')
|
segment = SegmentFactory(name="Multiple queries")
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(parameter="test", value="test", segment=segment)
|
||||||
parameter="test",
|
|
||||||
value="test",
|
|
||||||
segment=segment
|
|
||||||
)
|
|
||||||
|
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(
|
||||||
parameter="query",
|
parameter="query",
|
||||||
@ -50,77 +40,65 @@ def test_request_multiple_queries(client, site):
|
|||||||
segment=segment,
|
segment=segment,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/?test=test&query=value')
|
response = client.get("/?test=test&query=value")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert any(
|
assert any(
|
||||||
item['encoded_name'] == 'multiple-queries'
|
item["encoded_name"] == "multiple-queries"
|
||||||
for item in client.session['segments']
|
for item in client.session["segments"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_persistent_segmenting(client, site):
|
def test_request_persistent_segmenting(client, site):
|
||||||
segment = SegmentFactory(name='Persistent', persistent=True)
|
segment = SegmentFactory(name="Persistent", persistent=True)
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(parameter="test", value="test", segment=segment)
|
||||||
parameter="test",
|
|
||||||
value="test",
|
response = client.get("/?test=test")
|
||||||
segment=segment
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
assert any(
|
||||||
|
item["encoded_name"] == "persistent" for item in client.session["segments"]
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.get('/?test=test')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
assert any(
|
|
||||||
item['encoded_name'] == 'persistent'
|
|
||||||
for item in client.session['segments'])
|
|
||||||
|
|
||||||
response = client.get('/')
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert any(
|
assert any(
|
||||||
item['encoded_name'] == 'persistent'
|
item["encoded_name"] == "persistent" for item in client.session["segments"]
|
||||||
for item in client.session['segments'])
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_non_persistent_segmenting(client, site):
|
def test_request_non_persistent_segmenting(client, site):
|
||||||
segment = SegmentFactory(name='Non Persistent')
|
segment = SegmentFactory(name="Non Persistent")
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(parameter="test", value="test", segment=segment)
|
||||||
parameter="test",
|
|
||||||
value="test",
|
|
||||||
segment=segment
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get('/?test=test')
|
response = client.get("/?test=test")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert any(
|
assert any(
|
||||||
item['encoded_name'] == 'non-persistent'
|
item["encoded_name"] == "non-persistent" for item in client.session["segments"]
|
||||||
for item in client.session['segments'])
|
)
|
||||||
|
|
||||||
response = client.get('/')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert not any(
|
assert not any(
|
||||||
item['encoded_name'] == 'non-persistent'
|
item["encoded_name"] == "non-persistent" for item in client.session["segments"]
|
||||||
for item in client.session['segments'])
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_request_match_any_segmenting(client, site):
|
def test_request_match_any_segmenting(client, site):
|
||||||
segment = SegmentFactory(name='Match any', match_any=True)
|
segment = SegmentFactory(name="Match any", match_any=True)
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(
|
||||||
parameter='test',
|
parameter="test",
|
||||||
value='test',
|
value="test",
|
||||||
segment=segment,
|
segment=segment,
|
||||||
)
|
)
|
||||||
QueryRuleFactory(
|
QueryRuleFactory(parameter="test2", value="test2", segment=segment)
|
||||||
parameter='test2',
|
|
||||||
value='test2',
|
|
||||||
segment=segment
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get('/?test=test')
|
response = client.get("/?test=test")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert any(
|
assert any(
|
||||||
item['encoded_name'] == 'match-any'
|
item["encoded_name"] == "match-any" for item in client.session["segments"]
|
||||||
for item in client.session['segments'])
|
)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tests.factories.rule import ReferralRuleFactory
|
from tests.factories.rule import ReferralRuleFactory
|
||||||
@ -7,9 +6,7 @@ from tests.factories.segment import SegmentFactory
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_referral_rule_create():
|
def test_referral_rule_create():
|
||||||
segment = SegmentFactory(name='Referral')
|
segment = SegmentFactory(name="Referral")
|
||||||
referral_rule = ReferralRuleFactory(
|
referral_rule = ReferralRuleFactory(regex_string="test.test", segment=segment)
|
||||||
regex_string='test.test',
|
|
||||||
segment=segment)
|
|
||||||
|
|
||||||
assert referral_rule.regex_string == 'test.test'
|
assert referral_rule.regex_string == "test.test"
|
||||||
|
@ -9,11 +9,12 @@ from tests.factories.segment import SegmentFactory
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_time_rule_create():
|
def test_time_rule_create():
|
||||||
segment = SegmentFactory(name='TimeSegment')
|
segment = SegmentFactory(name="TimeSegment")
|
||||||
time_rule = TimeRuleFactory(
|
time_rule = TimeRuleFactory(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=segment)
|
segment=segment,
|
||||||
|
)
|
||||||
|
|
||||||
assert time_rule.start_time == datetime.time(8, 0, 0)
|
assert time_rule.start_time == datetime.time(8, 0, 0)
|
||||||
|
|
||||||
@ -21,13 +22,14 @@ def test_time_rule_create():
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@freeze_time("10:00:00")
|
@freeze_time("10:00:00")
|
||||||
def test_requesttime_segment(client, site):
|
def test_requesttime_segment(client, site):
|
||||||
time_only_segment = SegmentFactory(name='Time only')
|
time_only_segment = SegmentFactory(name="Time only")
|
||||||
TimeRuleFactory(
|
TimeRuleFactory(
|
||||||
start_time=datetime.time(8, 0, 0),
|
start_time=datetime.time(8, 0, 0),
|
||||||
end_time=datetime.time(23, 0, 0),
|
end_time=datetime.time(23, 0, 0),
|
||||||
segment=time_only_segment)
|
segment=time_only_segment,
|
||||||
|
)
|
||||||
|
|
||||||
response = client.get('/')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert client.session['segments'][0]['encoded_name'] == 'time-only'
|
assert client.session["segments"][0]["encoded_name"] == "time-only"
|
||||||
|
@ -7,23 +7,23 @@ from wagtail_personalisation.rules import VisitCountRule
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_visit_count(site, client):
|
def test_visit_count(site, client):
|
||||||
response = client.get('/')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
visit_count = client.session['visit_count']
|
visit_count = client.session["visit_count"]
|
||||||
assert visit_count[0]['path'] == '/'
|
assert visit_count[0]["path"] == "/"
|
||||||
assert visit_count[0]['count'] == 1
|
assert visit_count[0]["count"] == 1
|
||||||
|
|
||||||
response = client.get('/')
|
response = client.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
visit_count = client.session['visit_count']
|
visit_count = client.session["visit_count"]
|
||||||
assert visit_count[0]['path'] == '/'
|
assert visit_count[0]["path"] == "/"
|
||||||
assert visit_count[0]['count'] == 2
|
assert visit_count[0]["count"] == 2
|
||||||
|
|
||||||
response = client.get('/page-1/')
|
response = client.get("/page-1/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
visit_count = client.session['visit_count']
|
visit_count = client.session["visit_count"]
|
||||||
assert visit_count[0]['count'] == 2
|
assert visit_count[0]["count"] == 2
|
||||||
assert visit_count[1]['count'] == 1
|
assert visit_count[1]["count"] == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@ -34,11 +34,11 @@ def test_call_test_user_on_invalid_rule_fails(site, user, mocker):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_visit_count_call_test_user_with_user(site, client, user):
|
def test_visit_count_call_test_user_with_user(site, client, user):
|
||||||
segment = SegmentFactory(name='VisitCount')
|
segment = SegmentFactory(name="VisitCount")
|
||||||
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
||||||
|
|
||||||
session = client.session
|
session = client.session
|
||||||
session['visit_count'] = [{'path': '/', 'count': 2}]
|
session["visit_count"] = [{"path": "/", "count": 2}]
|
||||||
session.save()
|
session.save()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
|
|
||||||
@ -47,11 +47,11 @@ def test_visit_count_call_test_user_with_user(site, client, user):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_visit_count_call_test_user_with_user_or_request_fails(site, client, user):
|
def test_visit_count_call_test_user_with_user_or_request_fails(site, client, user):
|
||||||
segment = SegmentFactory(name='VisitCount')
|
segment = SegmentFactory(name="VisitCount")
|
||||||
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
||||||
|
|
||||||
session = client.session
|
session = client.session
|
||||||
session['visit_count'] = [{'path': '/', 'count': 2}]
|
session["visit_count"] = [{"path": "/", "count": 2}]
|
||||||
session.save()
|
session.save()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
|
|
||||||
@ -60,20 +60,20 @@ def test_visit_count_call_test_user_with_user_or_request_fails(site, client, use
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_column_header(site):
|
def test_get_column_header(site):
|
||||||
segment = SegmentFactory(name='VisitCount')
|
segment = SegmentFactory(name="VisitCount")
|
||||||
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
||||||
|
|
||||||
assert rule.get_column_header() == 'Visit count - Test page'
|
assert rule.get_column_header() == "Visit count - Test page"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_user_info_string_returns_count(site, client, user):
|
def test_get_user_info_string_returns_count(site, client, user):
|
||||||
segment = SegmentFactory(name='VisitCount')
|
segment = SegmentFactory(name="VisitCount")
|
||||||
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
|
||||||
|
|
||||||
session = client.session
|
session = client.session
|
||||||
session['visit_count'] = [{'path': '/', 'count': 2}]
|
session["visit_count"] = [{"path": "/", "count": 2}]
|
||||||
session.save()
|
session.save()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
|
|
||||||
assert rule.get_user_info_string(user) == '2'
|
assert rule.get_user_info_string(user) == "2"
|
||||||
|
@ -12,12 +12,31 @@ from wagtail_personalisation.rules import TimeRule, VisitCountRule
|
|||||||
|
|
||||||
|
|
||||||
def form_with_data(segment, *rules):
|
def form_with_data(segment, *rules):
|
||||||
model_fields = ['type', 'status', 'count', 'name', 'match_any', 'randomisation_percent']
|
model_fields = [
|
||||||
|
"type",
|
||||||
|
"status",
|
||||||
|
"count",
|
||||||
|
"name",
|
||||||
|
"match_any",
|
||||||
|
"randomisation_percent",
|
||||||
|
]
|
||||||
|
|
||||||
|
model_formsets = [
|
||||||
|
"wagtail_personalisation_timerules",
|
||||||
|
"wagtail_personalisation_dayrules",
|
||||||
|
"wagtail_personalisation_referralrules",
|
||||||
|
"wagtail_personalisation_visitcountrules",
|
||||||
|
"wagtail_personalisation_queryrules",
|
||||||
|
"wagtail_personalisation_devicerules",
|
||||||
|
"wagtail_personalisation_userisloggedinrules",
|
||||||
|
"wagtail_personalisation_origincountryrules",
|
||||||
|
]
|
||||||
|
|
||||||
class TestSegmentAdminForm(SegmentAdminForm):
|
class TestSegmentAdminForm(SegmentAdminForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Segment
|
model = Segment
|
||||||
fields = model_fields
|
fields = model_fields
|
||||||
|
formsets = model_formsets
|
||||||
|
|
||||||
data = model_to_dict(segment, model_fields)
|
data = model_to_dict(segment, model_fields)
|
||||||
for formset in TestSegmentAdminForm().formsets.values():
|
for formset in TestSegmentAdminForm().formsets.values():
|
||||||
@ -27,10 +46,10 @@ def form_with_data(segment, *rules):
|
|||||||
if isinstance(rule, formset.model):
|
if isinstance(rule, formset.model):
|
||||||
rule_data = model_to_dict(rule)
|
rule_data = model_to_dict(rule)
|
||||||
for key, value in rule_data.items():
|
for key, value in rule_data.items():
|
||||||
data['{}-{}-{}'.format(formset.prefix, count, key)] = value
|
data["{}-{}-{}".format(formset.prefix, count, key)] = value
|
||||||
count += 1
|
count += 1
|
||||||
data['{}-INITIAL_FORMS'.format(formset.prefix)] = 0
|
data["{}-INITIAL_FORMS".format(formset.prefix)] = 0
|
||||||
data['{}-TOTAL_FORMS'.format(formset.prefix)] = count
|
data["{}-TOTAL_FORMS".format(formset.prefix)] = count
|
||||||
return TestSegmentAdminForm(data)
|
return TestSegmentAdminForm(data)
|
||||||
|
|
||||||
|
|
||||||
@ -39,21 +58,27 @@ def test_user_added_to_static_segment_at_creation(site, user, mocker):
|
|||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert user in instance.static_users.all()
|
assert user in instance.static_users.all()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_not_added_to_full_static_segment_at_creation(site, django_user_model, mocker):
|
def test_user_not_added_to_full_static_segment_at_creation(
|
||||||
django_user_model.objects.create(username='first')
|
site, django_user_model, mocker
|
||||||
django_user_model.objects.create(username='second')
|
):
|
||||||
|
django_user_model.objects.create(username="first")
|
||||||
|
django_user_model.objects.create(username="second")
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user',
|
mocker.patch(
|
||||||
side_effect=[True, True])
|
"wagtail_personalisation.rules.VisitCountRule.test_user",
|
||||||
|
side_effect=[True, True],
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert len(instance.static_users.all()) == 1
|
assert len(instance.static_users.all()) == 1
|
||||||
@ -68,7 +93,9 @@ def test_anonymous_user_not_added_to_static_segment_at_creation(site, client, mo
|
|||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mock_test_rule = mocker.patch('wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
mock_test_rule = mocker.patch(
|
||||||
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules"
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert not instance.static_users.all()
|
assert not instance.static_users.all()
|
||||||
@ -77,15 +104,18 @@ def test_anonymous_user_not_added_to_static_segment_at_creation(site, client, mo
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_match_any_correct_populates(site, django_user_model, mocker):
|
def test_match_any_correct_populates(site, django_user_model, mocker):
|
||||||
user = django_user_model.objects.create(username='first')
|
user = django_user_model.objects.create(username="first")
|
||||||
other_user = django_user_model.objects.create(username='second')
|
other_user = django_user_model.objects.create(username="second")
|
||||||
other_page = site.root_page.get_last_child()
|
other_page = site.root_page.get_last_child()
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, match_any=True)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, match_any=True)
|
||||||
rule_1 = VisitCountRule(counted_page=site.root_page)
|
rule_1 = VisitCountRule(counted_page=site.root_page)
|
||||||
rule_2 = VisitCountRule(counted_page=other_page)
|
rule_2 = VisitCountRule(counted_page=other_page)
|
||||||
form = form_with_data(segment, rule_1, rule_2)
|
form = form_with_data(segment, rule_1, rule_2)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', side_effect=[True, False, True, False])
|
mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user",
|
||||||
|
side_effect=[True, False, True, False],
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert user in instance.static_users.all()
|
assert user in instance.static_users.all()
|
||||||
@ -102,7 +132,9 @@ def test_mixed_static_dynamic_session_doesnt_generate_at_creation(site, mocker):
|
|||||||
)
|
)
|
||||||
form = form_with_data(segment, static_rule, non_static_rule)
|
form = form_with_data(segment, static_rule, non_static_rule)
|
||||||
|
|
||||||
mock_test_rule = mocker.patch('wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
mock_test_rule = mocker.patch(
|
||||||
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules"
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert not instance.static_users.all()
|
assert not instance.static_users.all()
|
||||||
@ -154,9 +186,11 @@ def test_anonymou_user_not_added_to_static_segment_after_creation(site, client):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_session_not_added_to_static_segment_after_full(site, client, django_user_model):
|
def test_session_not_added_to_static_segment_after_full(
|
||||||
user = django_user_model.objects.create(username='first')
|
site, client, django_user_model
|
||||||
other_user = django_user_model.objects.create(username='second')
|
):
|
||||||
|
user = django_user_model.objects.create(username="first")
|
||||||
|
other_user = django_user_model.objects.create(username="second")
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
@ -190,7 +224,9 @@ def test_sessions_not_added_to_static_segment_if_rule_not_static(mocker):
|
|||||||
segment=segment,
|
segment=segment,
|
||||||
)
|
)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mock_test_rule = mocker.patch('wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
mock_test_rule = mocker.patch(
|
||||||
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules"
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert not instance.static_users.all()
|
assert not instance.static_users.all()
|
||||||
@ -202,12 +238,16 @@ def test_does_not_calculate_the_segment_again(site, client, mocker, user):
|
|||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=2)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=2)
|
||||||
rule = VisitCountRule(counted_page=site.root_page, segment=segment)
|
rule = VisitCountRule(counted_page=site.root_page, segment=segment)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert user in instance.static_users.all()
|
assert user in instance.static_users.all()
|
||||||
|
|
||||||
mock_test_rule = mocker.patch('wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
mock_test_rule = mocker.patch(
|
||||||
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules"
|
||||||
|
)
|
||||||
session = client.session
|
session = client.session
|
||||||
session.save()
|
session.save()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
@ -247,7 +287,9 @@ def test_dynamic_segment_with_non_static_rules_have_a_count():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_randomisation_percentage_added_to_segment_at_creation(site, client, mocker, django_user_model):
|
def test_randomisation_percentage_added_to_segment_at_creation(
|
||||||
|
site, client, mocker, django_user_model
|
||||||
|
):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
segment.randomisation_percent = 80
|
segment.randomisation_percent = 80
|
||||||
rule = VisitCountRule()
|
rule = VisitCountRule()
|
||||||
@ -280,80 +322,85 @@ def test_randomisation_percentage_max_100(site, client, mocker, django_user_mode
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_in_static_segment_if_random_is_below_percentage(site, client, mocker, user):
|
def test_in_static_segment_if_random_is_below_percentage(site, client, mocker, user):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1,
|
segment = SegmentFactory.build(
|
||||||
randomisation_percent=40)
|
type=Segment.TYPE_STATIC, count=1, randomisation_percent=40
|
||||||
|
)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
mocker.patch('random.randint', return_value=39)
|
mocker.patch("random.randint", return_value=39)
|
||||||
session = client.session
|
session = client.session
|
||||||
session.save()
|
session.save()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert instance.id == client.session['segments'][0]['id']
|
assert instance.id == client.session["segments"][0]["id"]
|
||||||
assert user in instance.static_users.all()
|
assert user in instance.static_users.all()
|
||||||
assert user not in instance.excluded_users.all()
|
assert user not in instance.excluded_users.all()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_not_in_static_segment_if_random_is_above_percentage(site, client, mocker, user):
|
def test_not_in_static_segment_if_random_is_above_percentage(
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1,
|
site, client, mocker, user
|
||||||
randomisation_percent=40)
|
):
|
||||||
|
segment = SegmentFactory.build(
|
||||||
|
type=Segment.TYPE_STATIC, count=1, randomisation_percent=40
|
||||||
|
)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
mocker.patch('random.randint', return_value=41)
|
mocker.patch("random.randint", return_value=41)
|
||||||
session = client.session
|
session = client.session
|
||||||
session.save()
|
session.save()
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
assert user not in instance.static_users.all()
|
assert user not in instance.static_users.all()
|
||||||
assert user in instance.excluded_users.all()
|
assert user in instance.excluded_users.all()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_offered_dynamic_segment_if_random_is_below_percentage(site, client, mocker):
|
def test_offered_dynamic_segment_if_random_is_below_percentage(site, client, mocker):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC,
|
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC, randomisation_percent=40)
|
||||||
randomisation_percent=40)
|
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
mocker.patch('random.randint', return_value=39)
|
mocker.patch("random.randint", return_value=39)
|
||||||
session = client.session
|
session = client.session
|
||||||
session.save()
|
session.save()
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert len(client.session['excluded_segments']) == 0
|
assert len(client.session["excluded_segments"]) == 0
|
||||||
assert instance.id == client.session['segments'][0]['id']
|
assert instance.id == client.session["segments"][0]["id"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_not_offered_dynamic_segment_if_random_is_above_percentage(site, client, mocker):
|
def test_not_offered_dynamic_segment_if_random_is_above_percentage(
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC,
|
site, client, mocker
|
||||||
randomisation_percent=40)
|
):
|
||||||
|
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC, randomisation_percent=40)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
mocker.patch('random.randint', return_value=41)
|
mocker.patch("random.randint", return_value=41)
|
||||||
session = client.session
|
session = client.session
|
||||||
session.save()
|
session.save()
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
assert instance.id == client.session['excluded_segments'][0]['id']
|
assert instance.id == client.session["excluded_segments"][0]["id"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_not_in_segment_if_percentage_is_0(site, client, mocker, user):
|
def test_not_in_segment_if_percentage_is_0(site, client, mocker, user):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1,
|
segment = SegmentFactory.build(
|
||||||
randomisation_percent=0)
|
type=Segment.TYPE_STATIC, count=1, randomisation_percent=0
|
||||||
|
)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
@ -363,15 +410,16 @@ def test_not_in_segment_if_percentage_is_0(site, client, mocker, user):
|
|||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
assert user not in instance.static_users.all()
|
assert user not in instance.static_users.all()
|
||||||
assert user in instance.excluded_users.all()
|
assert user in instance.excluded_users.all()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_always_in_segment_if_percentage_is_100(site, client, mocker, user):
|
def test_always_in_segment_if_percentage_is_100(site, client, mocker, user):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1,
|
segment = SegmentFactory.build(
|
||||||
randomisation_percent=100)
|
type=Segment.TYPE_STATIC, count=1, randomisation_percent=100
|
||||||
|
)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
@ -381,18 +429,22 @@ def test_always_in_segment_if_percentage_is_100(site, client, mocker, user):
|
|||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert instance.id == client.session['segments'][0]['id']
|
assert instance.id == client.session["segments"][0]["id"]
|
||||||
assert user in instance.static_users.all()
|
assert user in instance.static_users.all()
|
||||||
assert user not in instance.excluded_users.all()
|
assert user not in instance.excluded_users.all()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_not_added_to_static_segment_at_creation_if_random_above_percent(site, mocker, user):
|
def test_not_added_to_static_segment_at_creation_if_random_above_percent(
|
||||||
mocker.patch('random.randint', return_value=41)
|
site, mocker, user
|
||||||
|
):
|
||||||
|
mocker.patch("random.randint", return_value=41)
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, randomisation_percent=40)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, randomisation_percent=40)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert user not in instance.static_users.all()
|
assert user not in instance.static_users.all()
|
||||||
@ -400,12 +452,16 @@ def test_not_added_to_static_segment_at_creation_if_random_above_percent(site, m
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_added_to_static_segment_at_creation_if_random_below_percent(site, mocker, user):
|
def test_added_to_static_segment_at_creation_if_random_below_percent(
|
||||||
mocker.patch('random.randint', return_value=39)
|
site, mocker, user
|
||||||
|
):
|
||||||
|
mocker.patch("random.randint", return_value=39)
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, randomisation_percent=40)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, randomisation_percent=40)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert user in instance.static_users.all()
|
assert user in instance.static_users.all()
|
||||||
@ -414,8 +470,9 @@ def test_added_to_static_segment_at_creation_if_random_below_percent(site, mocke
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_rules_check_skipped_if_user_in_excluded(site, client, mocker, user):
|
def test_rules_check_skipped_if_user_in_excluded(site, client, mocker, user):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1,
|
segment = SegmentFactory.build(
|
||||||
randomisation_percent=100)
|
type=Segment.TYPE_STATIC, count=1, randomisation_percent=100
|
||||||
|
)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
@ -423,7 +480,8 @@ def test_rules_check_skipped_if_user_in_excluded(site, client, mocker, user):
|
|||||||
instance.save
|
instance.save
|
||||||
|
|
||||||
mock_test_rule = mocker.patch(
|
mock_test_rule = mocker.patch(
|
||||||
'wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules"
|
||||||
|
)
|
||||||
|
|
||||||
session = client.session
|
session = client.session
|
||||||
session.save()
|
session.save()
|
||||||
@ -431,15 +489,14 @@ def test_rules_check_skipped_if_user_in_excluded(site, client, mocker, user):
|
|||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert mock_test_rule.call_count == 0
|
assert mock_test_rule.call_count == 0
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
assert user not in instance.static_users.all()
|
assert user not in instance.static_users.all()
|
||||||
assert user in instance.excluded_users.all()
|
assert user in instance.excluded_users.all()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_rules_check_skipped_if_dynamic_segment_in_excluded(site, client, mocker, user):
|
def test_rules_check_skipped_if_dynamic_segment_in_excluded(site, client, mocker, user):
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC,
|
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC, randomisation_percent=100)
|
||||||
randomisation_percent=100)
|
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
@ -447,30 +504,35 @@ def test_rules_check_skipped_if_dynamic_segment_in_excluded(site, client, mocker
|
|||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
session = client.session
|
session = client.session
|
||||||
session['excluded_segments'] = [{'id': instance.pk}]
|
session["excluded_segments"] = [{"id": instance.pk}]
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
mock_test_rule = mocker.patch(
|
mock_test_rule = mocker.patch(
|
||||||
'wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
"wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules"
|
||||||
|
)
|
||||||
|
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
client.get(site.root_page.url)
|
client.get(site.root_page.url)
|
||||||
|
|
||||||
assert mock_test_rule.call_count == 0
|
assert mock_test_rule.call_count == 0
|
||||||
assert len(client.session['segments']) == 0
|
assert len(client.session["segments"]) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_matched_user_count_added_to_segment_at_creation(site, mocker, django_user_model):
|
def test_matched_user_count_added_to_segment_at_creation(
|
||||||
django_user_model.objects.create(username='first')
|
site, mocker, django_user_model
|
||||||
django_user_model.objects.create(username='second')
|
):
|
||||||
|
django_user_model.objects.create(username="first")
|
||||||
|
django_user_model.objects.create(username="second")
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = VisitCountRule()
|
rule = VisitCountRule()
|
||||||
|
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
form.instance.type = Segment.TYPE_STATIC
|
form.instance.type = Segment.TYPE_STATIC
|
||||||
mock_test_user = mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mock_test_user = mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
|
|
||||||
assert mock_test_user.call_count == 2
|
assert mock_test_user.call_count == 2
|
||||||
@ -479,49 +541,59 @@ def test_matched_user_count_added_to_segment_at_creation(site, mocker, django_us
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_count_users_matching_static_rules(site, client, mocker, django_user_model):
|
def test_count_users_matching_static_rules(site, client, mocker, django_user_model):
|
||||||
django_user_model.objects.create(username='first')
|
django_user_model.objects.create(username="first")
|
||||||
django_user_model.objects.create(username='second')
|
django_user_model.objects.create(username="second")
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
assert form.count_matching_users([rule], True) == 2
|
assert form.count_matching_users([rule], True) == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_count_matching_users_excludes_staff(site, client, mocker, django_user_model):
|
def test_count_matching_users_excludes_staff(site, client, mocker, django_user_model):
|
||||||
django_user_model.objects.create(username='first')
|
django_user_model.objects.create(username="first")
|
||||||
django_user_model.objects.create(username='second', is_staff=True)
|
django_user_model.objects.create(username="second", is_staff=True)
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mock_test_user = mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mock_test_user = mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
assert form.count_matching_users([rule], True) == 1
|
assert form.count_matching_users([rule], True) == 1
|
||||||
assert mock_test_user.call_count == 1
|
assert mock_test_user.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_count_matching_users_excludes_inactive(site, client, mocker, django_user_model):
|
def test_count_matching_users_excludes_inactive(
|
||||||
django_user_model.objects.create(username='first')
|
site, client, mocker, django_user_model
|
||||||
django_user_model.objects.create(username='second', is_active=False)
|
):
|
||||||
|
django_user_model.objects.create(username="first")
|
||||||
|
django_user_model.objects.create(username="second", is_active=False)
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = VisitCountRule(counted_page=site.root_page)
|
rule = VisitCountRule(counted_page=site.root_page)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mock_test_user = mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
|
mock_test_user = mocker.patch(
|
||||||
|
"wagtail_personalisation.rules.VisitCountRule.test_user", return_value=True
|
||||||
|
)
|
||||||
|
|
||||||
assert form.count_matching_users([rule], True) == 1
|
assert form.count_matching_users([rule], True) == 1
|
||||||
assert mock_test_user.call_count == 1
|
assert mock_test_user.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_count_matching_users_only_counts_static_rules(site, client, mocker, django_user_model):
|
def test_count_matching_users_only_counts_static_rules(
|
||||||
django_user_model.objects.create(username='first')
|
site, client, mocker, django_user_model
|
||||||
django_user_model.objects.create(username='second')
|
):
|
||||||
|
django_user_model.objects.create(username="first")
|
||||||
|
django_user_model.objects.create(username="second")
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
rule = TimeRule(
|
rule = TimeRule(
|
||||||
@ -530,16 +602,18 @@ def test_count_matching_users_only_counts_static_rules(site, client, mocker, dja
|
|||||||
segment=segment,
|
segment=segment,
|
||||||
)
|
)
|
||||||
form = form_with_data(segment, rule)
|
form = form_with_data(segment, rule)
|
||||||
mock_test_user = mocker.patch('wagtail_personalisation.rules.TimeRule.test_user')
|
mock_test_user = mocker.patch("wagtail_personalisation.rules.TimeRule.test_user")
|
||||||
|
|
||||||
assert form.count_matching_users([rule], True) == 0
|
assert form.count_matching_users([rule], True) == 0
|
||||||
assert mock_test_user.call_count == 0
|
assert mock_test_user.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_count_matching_users_handles_match_any(site, client, mocker, django_user_model):
|
def test_count_matching_users_handles_match_any(
|
||||||
django_user_model.objects.create(username='first')
|
site, client, mocker, django_user_model
|
||||||
django_user_model.objects.create(username='second')
|
):
|
||||||
|
django_user_model.objects.create(username="first")
|
||||||
|
django_user_model.objects.create(username="second")
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
first_rule = VisitCountRule(counted_page=site.root_page)
|
first_rule = VisitCountRule(counted_page=site.root_page)
|
||||||
@ -548,17 +622,20 @@ def test_count_matching_users_handles_match_any(site, client, mocker, django_use
|
|||||||
form = form_with_data(segment, first_rule, second_rule)
|
form = form_with_data(segment, first_rule, second_rule)
|
||||||
|
|
||||||
mock_test_user = mocker.patch(
|
mock_test_user = mocker.patch(
|
||||||
'wagtail_personalisation.rules.VisitCountRule.test_user',
|
"wagtail_personalisation.rules.VisitCountRule.test_user",
|
||||||
side_effect=[True, False, True, False])
|
side_effect=[True, False, True, False],
|
||||||
|
)
|
||||||
|
|
||||||
assert form.count_matching_users([first_rule, second_rule], True) == 2
|
assert form.count_matching_users([first_rule, second_rule], True) == 2
|
||||||
mock_test_user.call_count == 4
|
mock_test_user.call_count == 4
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_count_matching_users_handles_match_all(site, client, mocker, django_user_model):
|
def test_count_matching_users_handles_match_all(
|
||||||
django_user_model.objects.create(username='first')
|
site, client, mocker, django_user_model
|
||||||
django_user_model.objects.create(username='second')
|
):
|
||||||
|
django_user_model.objects.create(username="first")
|
||||||
|
django_user_model.objects.create(username="second")
|
||||||
|
|
||||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||||
first_rule = VisitCountRule(counted_page=site.root_page)
|
first_rule = VisitCountRule(counted_page=site.root_page)
|
||||||
@ -567,8 +644,9 @@ def test_count_matching_users_handles_match_all(site, client, mocker, django_use
|
|||||||
form = form_with_data(segment, first_rule, second_rule)
|
form = form_with_data(segment, first_rule, second_rule)
|
||||||
|
|
||||||
mock_test_user = mocker.patch(
|
mock_test_user = mocker.patch(
|
||||||
'wagtail_personalisation.rules.VisitCountRule.test_user',
|
"wagtail_personalisation.rules.VisitCountRule.test_user",
|
||||||
side_effect=[True, True, False, True])
|
side_effect=[True, True, False, True],
|
||||||
|
)
|
||||||
|
|
||||||
assert form.count_matching_users([first_rule, second_rule], False) == 1
|
assert form.count_matching_users([first_rule, second_rule], False) == 1
|
||||||
mock_test_user.call_count == 4
|
mock_test_user.call_count == 4
|
||||||
|
@ -11,33 +11,44 @@ from tests.utils import render_template
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_segment_template_block(rf, site):
|
def test_segment_template_block(rf, site):
|
||||||
SegmentFactory(name='test', persistent=True)
|
SegmentFactory(name="test", persistent=True)
|
||||||
|
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
|
|
||||||
request.session['segments'] = [{
|
request.session["segments"] = [
|
||||||
"encoded_name": 'test',
|
{
|
||||||
"id": 1,
|
"encoded_name": "test",
|
||||||
"timestamp": int(time.time()),
|
"id": 1,
|
||||||
"persistent": True
|
"timestamp": int(time.time()),
|
||||||
}]
|
"persistent": True,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
content = render_template("""
|
content = render_template(
|
||||||
|
"""
|
||||||
{% load wagtail_personalisation_tags %}
|
{% load wagtail_personalisation_tags %}
|
||||||
{% segment name='test' %}Test{% endsegment %}
|
{% segment name='test' %}Test{% endsegment %}
|
||||||
""", request=request).strip()
|
""",
|
||||||
|
request=request,
|
||||||
|
).strip()
|
||||||
|
|
||||||
assert content == "Test"
|
assert content == "Test"
|
||||||
|
|
||||||
content = render_template("""
|
content = render_template(
|
||||||
|
"""
|
||||||
{% load wagtail_personalisation_tags %}
|
{% load wagtail_personalisation_tags %}
|
||||||
{% segment name='test2' %}Test{% endsegment %}
|
{% segment name='test2' %}Test{% endsegment %}
|
||||||
""", request=request).strip()
|
""",
|
||||||
|
request=request,
|
||||||
|
).strip()
|
||||||
|
|
||||||
assert content == ""
|
assert content == ""
|
||||||
|
|
||||||
with pytest.raises(TemplateSyntaxError):
|
with pytest.raises(TemplateSyntaxError):
|
||||||
content = render_template("""
|
content = render_template(
|
||||||
|
"""
|
||||||
{% load wagtail_personalisation_tags %}
|
{% load wagtail_personalisation_tags %}
|
||||||
{% segment wrongname='test2' %}Test{% endsegment %}
|
{% segment wrongname='test2' %}Test{% endsegment %}
|
||||||
""", request=request).strip()
|
""",
|
||||||
|
request=request,
|
||||||
|
).strip()
|
||||||
|
@ -2,39 +2,43 @@ import pytest
|
|||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from wagtail.core.models import Page as WagtailPage
|
from wagtail.core.models import Page as WagtailPage
|
||||||
|
|
||||||
from tests.factories.page import (ContentPageFactory, PersonalisablePageMetadataFactory)
|
from tests.factories.page import ContentPageFactory, PersonalisablePageMetadataFactory
|
||||||
from wagtail_personalisation.utils import (
|
from wagtail_personalisation.utils import (
|
||||||
can_delete_pages, exclude_variants, get_client_ip, impersonate_other_page)
|
can_delete_pages,
|
||||||
|
exclude_variants,
|
||||||
|
get_client_ip,
|
||||||
|
impersonate_other_page,
|
||||||
|
)
|
||||||
|
|
||||||
locale_factory = False
|
locale_factory = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from tests.factories.page import LocaleFactory
|
from tests.factories.page import LocaleFactory # noqa
|
||||||
locale_factory = True
|
|
||||||
|
locale_factory = True # noqa
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def rootpage():
|
def rootpage():
|
||||||
return ContentPageFactory(parent=None, path='/', depth=0, title='root')
|
return ContentPageFactory(parent=None, path="/", depth=0, title="root")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def page(rootpage):
|
def page(rootpage):
|
||||||
return ContentPageFactory(parent=rootpage, path='/hi', title='Hi')
|
return ContentPageFactory(parent=rootpage, path="/hi", title="Hi")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def otherpage(rootpage):
|
def otherpage(rootpage):
|
||||||
return ContentPageFactory(parent=rootpage, path='/bye', title='Bye')
|
return ContentPageFactory(parent=rootpage, path="/bye", title="Bye")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_impersonate_other_page(page, otherpage):
|
def test_impersonate_other_page(page, otherpage):
|
||||||
impersonate_other_page(page, otherpage)
|
impersonate_other_page(page, otherpage)
|
||||||
assert page.title == otherpage.title == 'Bye'
|
assert page.title == otherpage.title == "Bye"
|
||||||
assert page.path == otherpage.path
|
assert page.path == otherpage.path
|
||||||
|
|
||||||
|
|
||||||
@ -50,58 +54,63 @@ def test_cannot_delete_pages_with_standard_user(user, segmented_page):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_client_ip_with_remote_addr(rf):
|
def test_get_client_ip_with_remote_addr(rf):
|
||||||
request = rf.get('/', REMOTE_ADDR='173.231.235.87')
|
request = rf.get("/", REMOTE_ADDR="173.231.235.87")
|
||||||
assert get_client_ip(request) == '173.231.235.87'
|
assert get_client_ip(request) == "173.231.235.87"
|
||||||
|
|
||||||
|
|
||||||
def test_get_client_ip_with_x_forwarded_for(rf):
|
def test_get_client_ip_with_x_forwarded_for(rf):
|
||||||
request = rf.get('/', HTTP_X_FORWARDED_FOR='173.231.235.87',
|
request = rf.get(
|
||||||
REMOTE_ADDR='10.0.23.24')
|
"/", HTTP_X_FORWARDED_FOR="173.231.235.87", REMOTE_ADDR="10.0.23.24"
|
||||||
assert get_client_ip(request) == '173.231.235.87'
|
)
|
||||||
|
assert get_client_ip(request) == "173.231.235.87"
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(WAGTAIL_PERSONALISATION_IP_FUNCTION="some.non.existent.path")
|
||||||
WAGTAIL_PERSONALISATION_IP_FUNCTION='some.non.existent.path'
|
|
||||||
)
|
|
||||||
def test_get_client_ip_custom_get_client_ip_function_does_not_exist(rf):
|
def test_get_client_ip_custom_get_client_ip_function_does_not_exist(rf):
|
||||||
with pytest.raises(ImportError):
|
with pytest.raises(ImportError):
|
||||||
get_client_ip(rf.get('/'))
|
get_client_ip(rf.get("/"))
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(WAGTAIL_PERSONALISATION_IP_FUNCTION="tests.utils.get_custom_ip")
|
||||||
WAGTAIL_PERSONALISATION_IP_FUNCTION='tests.utils.get_custom_ip'
|
|
||||||
)
|
|
||||||
def test_get_client_ip_custom_get_client_ip_used(rf):
|
def test_get_client_ip_custom_get_client_ip_used(rf):
|
||||||
assert get_client_ip(rf.get('/')) == '123.123.123.123'
|
assert get_client_ip(rf.get("/")) == "123.123.123.123"
|
||||||
|
|
||||||
|
|
||||||
def test_exclude_variants_with_pages_querysets():
|
def test_exclude_variants_with_pages_querysets():
|
||||||
'''
|
"""
|
||||||
Test that excludes variant works for querysets
|
Test that excludes variant works for querysets
|
||||||
'''
|
"""
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
page = ContentPageFactory(path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i))
|
page = ContentPageFactory(
|
||||||
|
path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i)
|
||||||
|
)
|
||||||
page.save()
|
page.save()
|
||||||
pages = WagtailPage.objects.all().specific().order_by('id')
|
pages = WagtailPage.objects.all().specific().order_by("id")
|
||||||
|
|
||||||
result = exclude_variants(pages)
|
result = exclude_variants(pages)
|
||||||
assert type(result) == type(pages)
|
assert type(result) == type(pages)
|
||||||
assert set(result.values_list('pk', flat=True)) == set(pages.values_list('pk', flat=True))
|
assert set(result.values_list("pk", flat=True)) == set(
|
||||||
|
pages.values_list("pk", flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_exclude_variants_with_pages_querysets_not_canonical():
|
def test_exclude_variants_with_pages_querysets_not_canonical():
|
||||||
'''
|
"""
|
||||||
Test that excludes variant works for querysets with
|
Test that excludes variant works for querysets with
|
||||||
personalisation_metadata canonical False
|
personalisation_metadata canonical False
|
||||||
'''
|
"""
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
page = ContentPageFactory(path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i))
|
page = ContentPageFactory(
|
||||||
|
path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i)
|
||||||
|
)
|
||||||
page.save()
|
page.save()
|
||||||
pages = WagtailPage.objects.all().specific().order_by('id')
|
pages = WagtailPage.objects.all().specific().order_by("id")
|
||||||
# add variants
|
# add variants
|
||||||
for page in pages:
|
for page in pages:
|
||||||
variant = ContentPageFactory(title='variant %d' % page.pk)
|
variant = ContentPageFactory(title="variant %d" % page.pk)
|
||||||
page.personalisation_metadata = PersonalisablePageMetadataFactory(canonical_page=page, variant=variant)
|
page.personalisation_metadata = PersonalisablePageMetadataFactory(
|
||||||
|
canonical_page=page, variant=variant
|
||||||
|
)
|
||||||
page.save()
|
page.save()
|
||||||
|
|
||||||
pages = WagtailPage.objects.all().specific()
|
pages = WagtailPage.objects.all().specific()
|
||||||
@ -111,19 +120,25 @@ def test_exclude_variants_with_pages_querysets_not_canonical():
|
|||||||
|
|
||||||
|
|
||||||
def test_exclude_variants_with_pages_querysets_meta_none():
|
def test_exclude_variants_with_pages_querysets_meta_none():
|
||||||
'''
|
"""
|
||||||
Test that excludes variant works for querysets with meta as none
|
Test that excludes variant works for querysets with meta as none
|
||||||
'''
|
"""
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
page = ContentPageFactory(path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i))
|
page = ContentPageFactory(
|
||||||
|
path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i)
|
||||||
|
)
|
||||||
page.save()
|
page.save()
|
||||||
pages = WagtailPage.objects.all().specific().order_by('id')
|
pages = WagtailPage.objects.all().specific().order_by("id")
|
||||||
# add variants
|
# add variants
|
||||||
for page in pages:
|
for page in pages:
|
||||||
page.personalisation_metadata = PersonalisablePageMetadataFactory(canonical_page=page, variant=page)
|
page.personalisation_metadata = PersonalisablePageMetadataFactory(
|
||||||
|
canonical_page=page, variant=page
|
||||||
|
)
|
||||||
page.save()
|
page.save()
|
||||||
|
|
||||||
pages = WagtailPage.objects.all().specific()
|
pages = WagtailPage.objects.all().specific()
|
||||||
result = exclude_variants(pages)
|
result = exclude_variants(pages)
|
||||||
assert type(result) == type(pages)
|
assert type(result) == type(pages)
|
||||||
assert set(result.values_list('pk', flat=True)) == set(pages.values_list('pk', flat=True))
|
assert set(result.values_list("pk", flat=True)) == set(
|
||||||
|
pages.values_list("pk", flat=True)
|
||||||
|
)
|
||||||
|
@ -5,31 +5,29 @@ from wagtail.core.models import Page
|
|||||||
|
|
||||||
from wagtail_personalisation.models import Segment
|
from wagtail_personalisation.models import Segment
|
||||||
from wagtail_personalisation.rules import VisitCountRule
|
from wagtail_personalisation.rules import VisitCountRule
|
||||||
from wagtail_personalisation.views import (
|
from wagtail_personalisation.views import SegmentModelAdmin, SegmentModelDeleteView
|
||||||
SegmentModelAdmin, SegmentModelDeleteView)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_segment_user_data_view_requires_admin_access(site, client, django_user_model):
|
def test_segment_user_data_view_requires_admin_access(site, client, django_user_model):
|
||||||
user = django_user_model.objects.create(username='first')
|
user = django_user_model.objects.create(username="first")
|
||||||
|
|
||||||
segment = Segment(type=Segment.TYPE_STATIC, count=1)
|
segment = Segment(type=Segment.TYPE_STATIC, count=1)
|
||||||
segment.save()
|
segment.save()
|
||||||
|
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
url = reverse('segment:segment_user_data', args=(segment.id,))
|
url = reverse("segment:segment_user_data", args=(segment.id,))
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.url == '/admin/login/?next=%s' % url
|
assert response.url == "/admin/login/?next=%s" % url
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_segment_user_data_view(site, client, mocker, django_user_model):
|
def test_segment_user_data_view(site, client, mocker, django_user_model):
|
||||||
user1 = django_user_model.objects.create(username='first')
|
user1 = django_user_model.objects.create(username="first")
|
||||||
user2 = django_user_model.objects.create(username='second')
|
user2 = django_user_model.objects.create(username="second")
|
||||||
admin_user = django_user_model.objects.create(
|
admin_user = django_user_model.objects.create(username="admin", is_superuser=True)
|
||||||
username='admin', is_superuser=True)
|
|
||||||
|
|
||||||
segment = Segment(type=Segment.TYPE_STATIC, count=1)
|
segment = Segment(type=Segment.TYPE_STATIC, count=1)
|
||||||
segment.save()
|
segment.save()
|
||||||
@ -37,24 +35,28 @@ def test_segment_user_data_view(site, client, mocker, django_user_model):
|
|||||||
segment.static_users.add(user2)
|
segment.static_users.add(user2)
|
||||||
|
|
||||||
rule1 = VisitCountRule(counted_page=site.root_page, segment=segment)
|
rule1 = VisitCountRule(counted_page=site.root_page, segment=segment)
|
||||||
rule2 = VisitCountRule(counted_page=site.root_page.get_last_child(),
|
rule2 = VisitCountRule(
|
||||||
segment=segment)
|
counted_page=site.root_page.get_last_child(), segment=segment
|
||||||
|
)
|
||||||
rule1.save()
|
rule1.save()
|
||||||
rule2.save()
|
rule2.save()
|
||||||
|
|
||||||
mocker.patch('wagtail_personalisation.rules.VisitCountRule.get_user_info_string',
|
mocker.patch(
|
||||||
side_effect=[3, 9, 0, 1])
|
"wagtail_personalisation.rules.VisitCountRule.get_user_info_string",
|
||||||
|
side_effect=[3, 9, 0, 1],
|
||||||
|
)
|
||||||
|
|
||||||
client.force_login(admin_user)
|
client.force_login(admin_user)
|
||||||
response = client.get(
|
response = client.get(reverse("segment:segment_user_data", args=(segment.id,)))
|
||||||
reverse('segment:segment_user_data', args=(segment.id,)))
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data_lines = response.content.decode().split("\n")
|
data_lines = response.content.decode().split("\n")
|
||||||
|
|
||||||
assert data_lines[0] == 'Username,Visit count - Test page,Visit count - Regular page\r'
|
assert (
|
||||||
assert data_lines[1] == 'first,3,9\r'
|
data_lines[0] == "Username,Visit count - Test page,Visit count - Regular page\r"
|
||||||
assert data_lines[2] == 'second,0,1\r'
|
)
|
||||||
|
assert data_lines[1] == "first,3,9\r"
|
||||||
|
assert data_lines[2] == "second,0,1\r"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@ -64,9 +66,9 @@ def test_segment_delete_view_delete_instance(rf, segmented_page, user):
|
|||||||
segment = segmented_page.personalisation_metadata.segment
|
segment = segmented_page.personalisation_metadata.segment
|
||||||
canonical_page = segmented_page.personalisation_metadata.canonical_page
|
canonical_page = segmented_page.personalisation_metadata.canonical_page
|
||||||
variants_metadata = segment.get_used_pages()
|
variants_metadata = segment.get_used_pages()
|
||||||
page_variants = Page.objects.filter(pk__in=(
|
page_variants = Page.objects.filter(
|
||||||
variants_metadata.values_list('variant_id', flat=True)
|
pk__in=(variants_metadata.values_list("variant_id", flat=True))
|
||||||
))
|
)
|
||||||
|
|
||||||
# Make sure all canonical page, variants and variants metadata exist
|
# Make sure all canonical page, variants and variants metadata exist
|
||||||
assert canonical_page
|
assert canonical_page
|
||||||
@ -74,11 +76,10 @@ def test_segment_delete_view_delete_instance(rf, segmented_page, user):
|
|||||||
assert variants_metadata
|
assert variants_metadata
|
||||||
|
|
||||||
# Delete the segment via the method on the view.
|
# Delete the segment via the method on the view.
|
||||||
request = rf.get('/'.format(segment.pk))
|
request = rf.get("/".format(segment.pk)) # noqa
|
||||||
request.user = user
|
request.user = user
|
||||||
view = SegmentModelDeleteView(
|
view = SegmentModelDeleteView(
|
||||||
instance_pk=str(segment.pk),
|
instance_pk=str(segment.pk), model_admin=SegmentModelAdmin()
|
||||||
model_admin=SegmentModelAdmin()
|
|
||||||
)
|
)
|
||||||
view.request = request
|
view.request = request
|
||||||
view.delete_instance()
|
view.delete_instance()
|
||||||
@ -98,13 +99,12 @@ def test_segment_delete_view_delete_instance(rf, segmented_page, user):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_segment_delete_view_raises_permission_denied(rf, segmented_page, user):
|
def test_segment_delete_view_raises_permission_denied(rf, segmented_page, user):
|
||||||
segment = segmented_page.personalisation_metadata.segment
|
segment = segmented_page.personalisation_metadata.segment
|
||||||
request = rf.get('/'.format(segment.pk))
|
request = rf.get("/".format(segment.pk)) # noqa
|
||||||
request.user = user
|
request.user = user
|
||||||
view = SegmentModelDeleteView(
|
view = SegmentModelDeleteView(
|
||||||
instance_pk=str(segment.pk),
|
instance_pk=str(segment.pk), model_admin=SegmentModelAdmin()
|
||||||
model_admin=SegmentModelAdmin()
|
|
||||||
)
|
)
|
||||||
view.request = request
|
view.request = request
|
||||||
message = 'User have no permission to delete variant page objects.'
|
message = "User have no permission to delete variant page objects." # noqa
|
||||||
with pytest.raises(PermissionDenied):
|
with pytest.raises(PermissionDenied):
|
||||||
view.delete_instance()
|
view.delete_instance()
|
||||||
|
@ -10,7 +10,7 @@ from wagtail_personalisation import adapters, wagtail_hooks
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_serve_variant_no_variant(site, rf):
|
def test_serve_variant_no_variant(site, rf):
|
||||||
page = site.root_page
|
page = site.root_page
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
args = tuple()
|
args = tuple()
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ def test_serve_variant_no_variant(site, rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_variant_accessed_directly_returns_404(segmented_page, rf):
|
def test_variant_accessed_directly_returns_404(segmented_page, rf):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
args = tuple()
|
args = tuple()
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
with pytest.raises(Http404):
|
with pytest.raises(Http404):
|
||||||
@ -29,7 +29,7 @@ def test_variant_accessed_directly_returns_404(segmented_page, rf):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_serve_variant_with_variant_no_segment(site, rf, segmented_page):
|
def test_serve_variant_with_variant_no_segment(site, rf, segmented_page):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
args = tuple()
|
args = tuple()
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ def test_serve_variant_with_variant_no_segment(site, rf, segmented_page):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_serve_variant_with_variant_segmented(site, rf, segmented_page):
|
def test_serve_variant_with_variant_segmented(site, rf, segmented_page):
|
||||||
request = rf.get('/')
|
request = rf.get("/")
|
||||||
args = tuple()
|
args = tuple()
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ def test_serve_variant_with_variant_segmented(site, rf, segmented_page):
|
|||||||
def test_page_listing_variant_buttons(site, rf, segmented_page):
|
def test_page_listing_variant_buttons(site, rf, segmented_page):
|
||||||
page = segmented_page.personalisation_metadata.canonical_page
|
page = segmented_page.personalisation_metadata.canonical_page
|
||||||
|
|
||||||
SegmentFactory(name='something')
|
SegmentFactory(name="something")
|
||||||
result = wagtail_hooks.page_listing_variant_buttons(page, [])
|
result = wagtail_hooks.page_listing_variant_buttons(page, [])
|
||||||
items = list(result)
|
items = list(result)
|
||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
@ -68,47 +68,42 @@ def test_page_listing_variant_buttons(site, rf, segmented_page):
|
|||||||
def test_page_listing_more_buttons(site, rf, segmented_page):
|
def test_page_listing_more_buttons(site, rf, segmented_page):
|
||||||
page = segmented_page.personalisation_metadata.canonical_page
|
page = segmented_page.personalisation_metadata.canonical_page
|
||||||
|
|
||||||
SegmentFactory(name='something')
|
SegmentFactory(name="something")
|
||||||
result = wagtail_hooks.page_listing_more_buttons(page, [])
|
result = wagtail_hooks.page_listing_more_buttons(page, [])
|
||||||
items = list(result)
|
items = list(result)
|
||||||
assert len(items) == 3
|
assert len(items) == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_custom_delete_page_view_does_not_trigger_for_variants(
|
def test_custom_delete_page_view_does_not_trigger_for_variants(rf, segmented_page):
|
||||||
rf,
|
assert (wagtail_hooks.delete_related_variants(rf.get("/"), segmented_page)) is None
|
||||||
segmented_page
|
|
||||||
):
|
|
||||||
assert (
|
|
||||||
wagtail_hooks.delete_related_variants(rf.get('/'), segmented_page)
|
|
||||||
) is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_custom_delete_page_view_triggers_for_canonical_pages(
|
def test_custom_delete_page_view_triggers_for_canonical_pages(rf, segmented_page):
|
||||||
rf,
|
|
||||||
segmented_page
|
|
||||||
):
|
|
||||||
assert (
|
assert (
|
||||||
wagtail_hooks.delete_related_variants(
|
wagtail_hooks.delete_related_variants(
|
||||||
rf.get('/'),
|
rf.get("/"), segmented_page.personalisation_metadata.canonical_page
|
||||||
segmented_page.personalisation_metadata.canonical_page
|
|
||||||
)
|
)
|
||||||
) is not None
|
) is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_custom_delete_page_view_deletes_variants(rf, segmented_page, user):
|
def test_custom_delete_page_view_deletes_variants(rf, segmented_page, user):
|
||||||
post_request = rf.post('/')
|
post_request = rf.post("/")
|
||||||
user.is_superuser = True
|
user.is_superuser = True
|
||||||
rf.user = user
|
rf.user = user
|
||||||
canonical_page = segmented_page.personalisation_metadata.canonical_page
|
canonical_page = segmented_page.personalisation_metadata.canonical_page
|
||||||
canonical_page_variant = canonical_page.personalisation_metadata
|
canonical_page_variant = canonical_page.personalisation_metadata
|
||||||
assert canonical_page_variant
|
assert canonical_page_variant
|
||||||
|
|
||||||
variants = Page.objects.filter(pk__in=(
|
variants = Page.objects.filter(
|
||||||
canonical_page.personalisation_metadata.variants_metadata.values_list('variant_id', flat=True)
|
pk__in=(
|
||||||
))
|
canonical_page.personalisation_metadata.variants_metadata.values_list(
|
||||||
|
"variant_id", flat=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
variants_metadata = canonical_page.personalisation_metadata.variants_metadata
|
variants_metadata = canonical_page.personalisation_metadata.variants_metadata
|
||||||
# Make sure there are variants that exist in the database.
|
# Make sure there are variants that exist in the database.
|
||||||
assert len(variants.all())
|
assert len(variants.all())
|
||||||
@ -126,18 +121,20 @@ def test_custom_delete_page_view_deletes_variants(rf, segmented_page, user):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_custom_delete_page_view_deletes_variants_of_child_pages(rf, segmented_page, user):
|
def test_custom_delete_page_view_deletes_variants_of_child_pages(
|
||||||
|
rf, segmented_page, user
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Regression test for deleting pages that have children with variants
|
Regression test for deleting pages that have children with variants
|
||||||
"""
|
"""
|
||||||
post_request = rf.post('/')
|
post_request = rf.post("/")
|
||||||
user.is_superuser = True
|
user.is_superuser = True
|
||||||
rf.user = user
|
rf.user = user
|
||||||
canonical_page = segmented_page.personalisation_metadata.canonical_page
|
canonical_page = segmented_page.personalisation_metadata.canonical_page
|
||||||
# Create a child with a variant
|
# Create a child with a variant
|
||||||
child_page = ContentPageFactory(parent=canonical_page, slug='personalised-child')
|
child_page = ContentPageFactory(parent=canonical_page, slug="personalised-child")
|
||||||
child_page.personalisation_metadata.copy_for_segment(segmented_page.personalisation_metadata.segment)
|
child_page.personalisation_metadata.copy_for_segment(
|
||||||
# A ProtectedError would be raised if the bug persists
|
segmented_page.personalisation_metadata.segment
|
||||||
wagtail_hooks.delete_related_variants(
|
|
||||||
post_request, canonical_page
|
|
||||||
)
|
)
|
||||||
|
# A ProtectedError would be raised if the bug persists
|
||||||
|
wagtail_hooks.delete_related_variants(post_request, canonical_page)
|
||||||
|
@ -2,10 +2,10 @@ from django.template import engines
|
|||||||
|
|
||||||
|
|
||||||
def render_template(value, **context):
|
def render_template(value, **context):
|
||||||
template = engines['django'].from_string(value)
|
template = engines["django"].from_string(value)
|
||||||
request = context.pop('request', None)
|
request = context.pop("request", None)
|
||||||
return template.render(context, request)
|
return template.render(context, request)
|
||||||
|
|
||||||
|
|
||||||
def get_custom_ip(request):
|
def get_custom_ip(request):
|
||||||
return '123.123.123.123'
|
return "123.123.123.123"
|
||||||
|
20
tox.ini
20
tox.ini
@ -1,33 +1,41 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
flake8
|
flake8
|
||||||
py{36,37,38}-dj{22}-wt{211,212,213}
|
py{37,38}-dj{22}-wt{211,212,213}
|
||||||
py{37,38}-dj{30,31}-wt{211,212,213}
|
py{37,38,39}-dj{30,31}-wt{211,212,213}
|
||||||
|
py{37,38,39}-dj{30,31,32}-wt{214,215}
|
||||||
|
py{39}-dj{32,40}-wt{216,30}
|
||||||
|
|
||||||
[gh-actions]
|
[gh-actions]
|
||||||
python =
|
python =
|
||||||
3.6: py36
|
|
||||||
3.7: py37
|
3.7: py37
|
||||||
3.8: py38
|
3.8: py38
|
||||||
|
3.9: py39
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
py36: python3.6
|
|
||||||
py37: python3.7
|
py37: python3.7
|
||||||
py38: python3.8
|
py38: python3.8
|
||||||
|
py39: python3.9
|
||||||
commands = coverage run --parallel -m pytest -rs {posargs}
|
commands = coverage run --parallel -m pytest -rs {posargs}
|
||||||
extras = test
|
extras = test
|
||||||
deps =
|
deps =
|
||||||
dj22: Django>=2.2.8,<2.3
|
dj22: Django>=2.2.8,<2.3
|
||||||
dj30: Django>=3.0,<3.1
|
dj30: Django>=3.0,<3.1
|
||||||
dj31: Django>=3.1,<3.2
|
dj31: Django>=3.1,<3.2
|
||||||
|
dj32: Django>=3.2,<3.3
|
||||||
|
dj40: Django>=4.0,<4.1
|
||||||
wt211: wagtail>=2.11,<2.12
|
wt211: wagtail>=2.11,<2.12
|
||||||
wt212: wagtail>=2.12,<2.13
|
wt212: wagtail>=2.12,<2.13
|
||||||
wt213: wagtail>=2.13,<2.14
|
wt213: wagtail>=2.13,<2.14
|
||||||
|
wt214: wagtail>=2.14,<2.15
|
||||||
|
wt215: wagtail>=2.15,<2.16
|
||||||
|
wt216: wagtail>=2.16,<2.17
|
||||||
|
wt30: wagtail>=3.0,<4.0
|
||||||
geoip2: geoip2
|
geoip2: geoip2
|
||||||
|
|
||||||
[testenv:coverage-report]
|
[testenv:coverage-report]
|
||||||
basepython = python3.6
|
basepython = python3.8
|
||||||
deps = coverage
|
deps = coverage
|
||||||
pip_pre = true
|
pip_pre = true
|
||||||
skip_install = true
|
skip_install = true
|
||||||
@ -35,7 +43,7 @@ commands =
|
|||||||
coverage report --include="src/**/" --omit="src/**/migrations/*.py"
|
coverage report --include="src/**/" --omit="src/**/migrations/*.py"
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
basepython = python3.6
|
basepython = python3.8
|
||||||
deps = flake8==3.5.0
|
deps = flake8==3.5.0
|
||||||
commands =
|
commands =
|
||||||
flake8 src tests setup.py
|
flake8 src tests setup.py
|
||||||
|
Reference in New Issue
Block a user