7

Compare commits

...

28 Commits

Author SHA1 Message Date
4eb5c2fe15 Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-28 21:10:58 +00:00
dd4530203f Bump version: 0.15.2 → 0.15.3 2022-02-04 15:12:42 +00:00
48955675be Use get_context_data override instead of get_context for Wagtail >= 2.15 (#230). Fix #228 2022-01-28 11:53:57 +00:00
a81c5b3560 Bump version: 0.15.1 → 0.15.2 2021-09-24 10:30:22 +02:00
53880228e4 Merge pull request #226 from mikedingjan/feature/remove-staticfiles-tag
Replace staticfiles with static tag (django removed the staticfiles)
2021-08-12 14:20:16 +02:00
2bee66d0ae Replace staticfiles with static tag (django removed the staticfiles) 2021-08-12 10:44:02 +02:00
16e24b6791 Bump version: 0.15.0 → 0.15.1 2021-07-13 17:01:35 +02:00
477bfb9665 Newer versions of Wagtail provide extra args for listing buttons 2021-07-13 16:40:41 +02:00
6108469047 Remove old versions from test matrix 2021-07-13 16:40:23 +02:00
686f180081 Bump version: 0.14.0 → 0.15.0 2021-07-09 11:00:14 +02:00
9b1dbe35cb fix(tox): use correct format command for current package 2021-06-28 12:15:24 +02:00
7e0594e341 fix(tox): add new tox setup for github actions 2021-06-28 12:13:55 +02:00
0c19456053 Merge pull request #212 from marcelhekking/make_compatible_with_latest_wagtail_version
Make compatible with latest wagtail version
2021-06-28 12:10:31 +02:00
18140f76ab chore(ci): trigger github actions on pr 2021-06-28 12:08:58 +02:00
88b17ceeb8 chore(ci): add github actions python test step 2021-06-28 12:06:43 +02:00
570de7d128 Flake-import failed 2021-06-24 08:38:06 +02:00
b82d5165c3 Take up wagtail 2.11 in Travis test matrix and tox settings 2021-06-24 08:16:29 +02:00
8d802dbbf4 Restore original travis settings 2021-06-24 07:58:11 +02:00
9274073c68 Fix test errors 2021-06-24 07:57:31 +02:00
1f1264cf95 Fix typo 2020-11-25 16:40:15 +01:00
3f16ad686e Remove obsolete line 2020-11-25 15:54:32 +01:00
7101b63122 Check backward compatibility with tox 2020-11-25 15:50:52 +01:00
ffd839159b Make changes backwards compatible 2020-11-25 12:08:42 +01:00
d074ef85b9 No need for these settings 2020-11-24 09:10:14 +01:00
f3e403bec6 Make compatible with latest Wagtail version (2.11.2) 2020-11-24 09:05:20 +01:00
137b5b411c Merge pull request #203 from davisnando/master
Fix is_authenticated 'bool' object is not callable error
2020-01-24 08:22:06 +01:00
39f3500813 Bump version: 0.13.0 → 0.14.0 2019-09-27 09:16:15 +02:00
06569a3cc1 Fix 'bool' object is not callable error 2019-08-27 11:43:39 +02:00
21 changed files with 318 additions and 167 deletions

89
.github/workflows/python-test.yml vendored Normal file
View File

@ -0,0 +1,89 @@
---
name: Python Tests
on: [push, pull_request]
jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: pip install tox
- name: Validate formatting
run: tox -e format
test:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
tox_env:
- py36-dj22-wt211
- py36-dj22-wt212
- py36-dj22-wt213
- py37-dj22-wt211
- py37-dj22-wt212
- py37-dj22-wt213
- py38-dj22-wt211
- py38-dj22-wt212
- py38-dj22-wt213
- py37-dj30-wt211
- py37-dj30-wt212
- py37-dj30-wt213
- py38-dj30-wt211
- py38-dj30-wt212
- py38-dj30-wt213
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
tox_env: py37-dj22-wt211
- python-version: 3.7
tox_env: py37-dj22-wt212
- python-version: 3.7
tox_env: py37-dj22-wt213
- python-version: 3.8
tox_env: py38-dj22-wt211
- python-version: 3.8
tox_env: py38-dj22-wt212
- python-version: 3.8
tox_env: py38-dj22-wt213
- python-version: 3.7
tox_env: py37-dj30-wt211
- python-version: 3.7
tox_env: py37-dj30-wt212
- python-version: 3.7
tox_env: py37-dj30-wt213
- python-version: 3.8
tox_env: py38-dj30-wt211
- python-version: 3.8
tox_env: py38-dj30-wt212
- python-version: 3.8
tox_env: py38-dj30-wt213
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Test with tox
run: tox -e ${{ matrix.tox_env }} --index-url=https://pypi.python.org/simple/
- name: Prepare artifacts
run: mkdir -p .coverage-data && mv .coverage.* .coverage-data/
- uses: actions/upload-artifact@master
with:
name: coverage-data
path: .coverage-data/

View File

@ -1,48 +0,0 @@
---
sudo: false
language: python
matrix:
include:
- python: 3.6
env: TOXENV=lint
- python: 3.6
env: TOXENV=py36-django20-wagtail20
- python: 3.6
env: TOXENV=py36-django20-wagtail20-geoip2
- python: 3.6
env: TOXENV=py36-django20-wagtail21
- python: 3.6
env: TOXENV=py36-django20-wagtail21-geoip2
- python: 3.6
env: TOXENV=py36-django20-wagtail22
- python: 3.6
env: TOXENV=py36-django20-wagtail22-geoip2
- python: 3.6
env: TOXENV=py36-django21-wagtail23
- python: 3.6
env: TOXENV=py36-django21-wagtail23-geoip2
- python: 3.6
env: TOXENV=py36-django21-wagtail24
- python: 3.6
env: TOXENV=py36-django21-wagtail24-geoip2
- python: 3.6
env: TOXENV=py36-django22-wagtail25
- python: 3.6
env: TOXENV=py36-django22-wagtail25-geoip2
- python: 3.6
env: TOXENV=py36-django22-wagtail26
- python: 3.6
env: TOXENV=py36-django22-wagtail26-geoip2
- python: 3.6
env: TOXENV=py36-django111-wagtail22
install:
- pip install tox codecov
script:
- tox
after_success:
- tox -e coverage-report
- codecov

25
CHANGES
View File

@ -1,3 +1,28 @@
0.15.3
=================
- Add wagtail >= 2.15 support with get_context_data override instead of get_context
0.15.2
=================
- Replace staticfiles tag with static
0.15.1
=================
- Remove old versions from test matrix
- Fix button support in wagtail admin for newer wagtail versions
0.15.0
=================
- Fix is_authenticated 'bool' object is not callable error
- Add wagtail <=2.11 support
- Use Github Actions to test package instead of Travis CI
0.14.0
=================
- Fix 'bool' object is not callable error
- Fix deleting descendants with variants when deleting a page
- Add wagtail 2.6 support
0.13.0 0.13.0
================= =================
- Merged Praekelt fork - Merged Praekelt fork

View File

@ -62,10 +62,10 @@ author = 'Lab Digital BV'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.13.0' version = '0.15.3'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.13.0' release = '0.15.3'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@ -14,6 +14,7 @@ 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
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)
@ -78,11 +79,14 @@ MIDDLEWARE = [
'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.core.middleware.SiteMiddleware',
'wagtail.contrib.redirects.middleware.RedirectMiddleware', 'wagtail.contrib.redirects.middleware.RedirectMiddleware',
] ]
if find_spec('wagtail.contrib.legacy'):
MIDDLEWARE += ('wagtail.contrib.legacy.sitemiddleware.SiteMiddleware',)
else:
MIDDLEWARE += ('wagtail.core.middleware.SiteMiddleware', )
ROOT_URLCONF = 'sandbox.urls' ROOT_URLCONF = 'sandbox.urls'
TEMPLATES = [ TEMPLATES = [

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.13.0 current_version = 0.15.3
commit = true commit = true
tag = true tag = true
tag_name = {new_version} tag_name = {new_version}
@ -28,4 +28,3 @@ omit = src/**/migrations/*.py
[bumpversion:file:setup.py] [bumpversion:file:setup.py]
[bumpversion:file:docs/conf.py] [bumpversion:file:docs/conf.py]

View File

@ -12,14 +12,14 @@ tests_require = [
'factory_boy==2.8.1', 'factory_boy==2.8.1',
'flake8-blind-except', 'flake8-blind-except',
'flake8-debugger', 'flake8-debugger',
'flake8-imports', 'flake8-isort',
'flake8', 'flake8',
'freezegun==0.3.8', 'freezegun==0.3.8',
'pytest-cov==2.5.1', 'pytest-cov==2.5.1',
'pytest-django==3.1.2', 'pytest-django==4.1.0',
'pytest-pythonpath==0.7.2', 'pytest-pythonpath==0.7.2',
'pytest-sugar==0.9.1', 'pytest-sugar==0.9.1',
'pytest==3.4.2', 'pytest==6.1.2',
'wagtail_factories==1.1.0', 'wagtail_factories==1.1.0',
'pytest-mock==1.6.3', 'pytest-mock==1.6.3',
] ]
@ -35,7 +35,7 @@ with open('README.rst') as fh:
setup( setup(
name='wagtail-personalisation', name='wagtail-personalisation',
version='0.13.0', version='0.15.3',
description='A Wagtail add-on for showing personalized content', description='A Wagtail add-on for showing personalized content',
author='Lab Digital BV and others', author='Lab Digital BV and others',
author_email='opensource@labdigital.nl', author_email='opensource@labdigital.nl',

View File

@ -1,19 +1,19 @@
from datetime import datetime from datetime import datetime
import functools
from importlib import import_module from importlib import import_module
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.contrib.staticfiles.templatetags.staticfiles import static from django.templatetags.static import static
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.utils.lru_cache import lru_cache
from django.utils.translation import ugettext_lazy as _ 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
@lru_cache(maxsize=1000) @functools.lru_cache(maxsize=1000)
def user_from_data(user_id): def user_from_data(user_id):
User = get_user_model() User = get_user_model()
try: try:

View File

@ -419,7 +419,7 @@ class UserIsLoggedInRule(AbstractBaseRule):
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 {

View File

@ -1,5 +1,5 @@
{% extends "modeladmin/index.html" %} {% extends "modeladmin/index.html" %}
{% load i18n l10n staticfiles modeladmin_tags %} {% load i18n l10n static modeladmin_tags %}
{% block titletag %}{{ view.get_meta_title }}{% endblock %} {% block titletag %}{{ view.get_meta_title }}{% endblock %}

View File

@ -1,5 +1,5 @@
{% extends "modeladmin/wagtail_personalisation/segment/base.html" %} {% extends "modeladmin/wagtail_personalisation/segment/base.html" %}
{% load i18n l10n staticfiles modeladmin_tags wagtail_personalisation_filters %} {% load i18n l10n static modeladmin_tags wagtail_personalisation_filters %}
{% block toggle_view %}to List {% endblock%} {% block toggle_view %}to List {% endblock%}

View File

@ -1,5 +1,4 @@
{% extends "modeladmin/wagtail_personalisation/segment/base.html" %} {% extends "modeladmin/wagtail_personalisation/segment/base.html" %}
{% load i18n l10n staticfiles modeladmin_tags wagtail_personalisation_filters %} {% load i18n l10n static modeladmin_tags wagtail_personalisation_filters %}
{% block toggle_view %}to Dashboard {% endblock%} {% block toggle_view %}to Dashboard {% endblock%}

View File

@ -9,9 +9,15 @@ from django.template.defaultfilters import pluralize
from django.urls import reverse from django.urls import 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.admin import messages from wagtail.admin import messages
from wagtail.admin.site_summary import PagesSummaryItem, SummaryItem from wagtail.admin.site_summary import PagesSummaryItem, SummaryItem
from wagtail.admin.views.pages import get_valid_next_url_from_request
try:
from wagtail.admin.views.pages.utils import get_valid_next_url_from_request
except ModuleNotFoundError:
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 import hooks
from wagtail.core.models import Page from wagtail.core.models import Page
@ -23,16 +29,18 @@ from wagtail_personalisation.models import PersonalisablePageMetadata
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@hooks.register('register_admin_urls') @hooks.register("register_admin_urls")
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(r'^personalisation/', include( url(
admin_urls, namespace='wagtail_personalisation')), r"^personalisation/",
include(admin_urls, namespace="wagtail_personalisation"),
)
] ]
@hooks.register('before_serve_page') @hooks.register("before_serve_page")
def set_visit_count(page, request, serve_args, serve_kwargs): def set_visit_count(page, request, serve_args, serve_kwargs):
"""Tests the provided rules to see if the request still belongs """Tests the provided rules to see if the request still belongs
to a segment. to a segment.
@ -47,7 +55,7 @@ def set_visit_count(page, request, serve_args, serve_kwargs):
adapter.add_page_visit(page) adapter.add_page_visit(page)
@hooks.register('before_serve_page') @hooks.register("before_serve_page")
def segment_user(page, request, serve_args, serve_kwargs): def segment_user(page, request, serve_args, serve_kwargs):
"""Apply a segment to a visitor before serving the page. """Apply a segment to a visitor before serving the page.
@ -60,7 +68,7 @@ def segment_user(page, request, serve_args, serve_kwargs):
adapter = get_segment_adapter(request) adapter = get_segment_adapter(request)
adapter.refresh() adapter.refresh()
forced_segment = request.GET.get('segment', None) forced_segment = request.GET.get("segment", None)
if request.user.is_superuser and forced_segment is not None: if request.user.is_superuser and forced_segment is not None:
segment = models.Segment.objects.filter(pk=forced_segment).first() segment = models.Segment.objects.filter(pk=forced_segment).first()
if segment: if segment:
@ -78,14 +86,14 @@ class UserbarSegmentedLinkItem:
Show as segment: {self.segment.name}</a></div>""" Show as segment: {self.segment.name}</a></div>"""
@hooks.register('construct_wagtail_userbar') @hooks.register("construct_wagtail_userbar")
def add_segment_link_items(request, items): def add_segment_link_items(request, items):
for item in models.Segment.objects.enabled(): for item in models.Segment.objects.enabled():
items.append(UserbarSegmentedLinkItem(item)) items.append(UserbarSegmentedLinkItem(item))
return items return items
@hooks.register('before_serve_page') @hooks.register("before_serve_page")
def serve_variant(page, request, serve_args, serve_kwargs): def serve_variant(page, request, serve_args, serve_kwargs):
"""Apply a segment to a visitor before serving the page. """Apply a segment to a visitor before serving the page.
@ -119,13 +127,13 @@ def serve_variant(page, request, serve_args, serve_kwargs):
return variant.serve(request, *serve_args, **serve_kwargs) return variant.serve(request, *serve_args, **serve_kwargs)
@hooks.register('construct_explorer_page_queryset') @hooks.register("construct_explorer_page_queryset")
def dont_show_variant(parent_page, pages, request): def dont_show_variant(parent_page, pages, request):
return utils.exclude_variants(pages) return utils.exclude_variants(pages)
@hooks.register('register_page_listing_buttons') @hooks.register("register_page_listing_buttons")
def page_listing_variant_buttons(page, page_perms, is_parent=False): def page_listing_variant_buttons(page, page_perms, is_parent=False, *args):
"""Adds page listing buttons to personalisable pages. Shows variants for """Adds page listing buttons to personalisable pages. Shows variants for
the page (if any) and a 'Create a new variant' button. the page (if any) and a 'Create a new variant' button.
@ -136,17 +144,18 @@ def page_listing_variant_buttons(page, page_perms, is_parent=False):
metadata = page.personalisation_metadata metadata = page.personalisation_metadata
if metadata.is_canonical: if metadata.is_canonical:
yield ButtonWithDropdownFromHook( yield ButtonWithDropdownFromHook(
_('Variants'), _("Variants"),
hook_name='register_page_listing_variant_buttons', hook_name="register_page_listing_variant_buttons",
page=page, page=page,
page_perms=page_perms, page_perms=page_perms,
is_parent=is_parent, is_parent=is_parent,
attrs={'target': '_blank', 'title': _('Create or edit a variant')}, attrs={"target": "_blank", "title": _("Create or edit a variant")},
priority=100) priority=100,
)
@hooks.register('register_page_listing_variant_buttons') @hooks.register("register_page_listing_variant_buttons")
def page_listing_more_buttons(page, page_perms, is_parent=False): def page_listing_more_buttons(page, page_perms, is_parent=False, *args):
"""Adds a 'more' button to personalisable pages allowing users to quickly """Adds a 'more' button to personalisable pages allowing users to quickly
create a new variant for the selected segment. create a new variant for the selected segment.
@ -157,48 +166,63 @@ def page_listing_more_buttons(page, page_perms, is_parent=False):
metadata = page.personalisation_metadata metadata = page.personalisation_metadata
for vm in metadata.variants_metadata: for vm in metadata.variants_metadata:
yield Button('%s variant' % (vm.segment.name), yield Button(
reverse('wagtailadmin_pages:edit', args=[vm.variant_id]), "%s variant" % (vm.segment.name),
attrs={"title": _('Edit this variant')}, reverse("wagtailadmin_pages:edit", args=[vm.variant_id]),
classes=("icon", "icon-fa-pencil"), attrs={"title": _("Edit this variant")},
priority=0) classes=("icon", "icon-fa-pencil"),
priority=0,
)
for segment in metadata.get_unused_segments(): for segment in metadata.get_unused_segments():
yield Button('%s variant' % (segment.name), yield Button(
reverse('segment:copy_page', args=[page.pk, segment.pk]), "%s variant" % (segment.name),
attrs={"title": _('Create this variant')}, reverse("segment:copy_page", args=[page.pk, segment.pk]),
classes=("icon", "icon-fa-plus"), attrs={"title": _("Create this variant")},
priority=100) classes=("icon", "icon-fa-plus"),
priority=100,
)
yield Button(_('Create a new segment'), yield Button(
reverse('wagtail_personalisation_segment_modeladmin_create'), _("Create a new segment"),
attrs={"title": _('Create a new segment')}, reverse("wagtail_personalisation_segment_modeladmin_create"),
classes=("icon", "icon-fa-snowflake-o"), attrs={"title": _("Create a new segment")},
priority=200) classes=("icon", "icon-fa-snowflake-o"),
priority=200,
)
class CorrectedPagesSummaryItem(PagesSummaryItem): class CorrectedPagesSummaryItem(PagesSummaryItem):
def get_context(self): def get_total_pages(self, context):
# Perform the same check as Wagtail to get the correct count. # Perform the same check as Wagtail to get the correct count.
# Only correct the count when a root page is available to the user. # Only correct the count when a root page is available to the user.
# The `PagesSummaryItem` will return a page count of 0 otherwise. # The `PagesSummaryItem` will return a page count of 0 otherwise.
# https://github.com/wagtail/wagtail/blob/5c9ff23e229acabad406c42c4e13cbaea32e6c15/wagtail/admin/site_summary.py#L38 # https://github.com/wagtail/wagtail/blob/5c9ff23e229acabad406c42c4e13cbaea32e6c15/wagtail/admin/site_summary.py#L38
context = super().get_context() root_page = context.get("root_page", None)
root_page = context.get('root_page', None)
if root_page: if root_page:
pages = utils.exclude_variants( pages = utils.exclude_variants(
Page.objects.descendant_of(root_page, inclusive=True)) Page.objects.descendant_of(root_page, inclusive=True)
)
page_count = pages.count() page_count = pages.count()
if root_page.is_root(): if root_page.is_root():
page_count -= 1 page_count -= 1
context['total_pages'] = page_count return page_count
return context if WAGTAIL_VERSION >= (2, 15):
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context["total_pages"] = self.get_total_pages(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")
def add_corrected_pages_summary_panel(request, items): def add_corrected_pages_summary_panel(request, items):
"""Replaces the Pages summary panel to hide variants.""" """Replaces the Pages summary panel to hide variants."""
for index, item in enumerate(items): for index, item in enumerate(items):
@ -211,28 +235,39 @@ class SegmentSummaryPanel(SummaryItem):
site and allowing quick access to the Segment dashboard. site and allowing quick access to the Segment dashboard.
""" """
order = 2000 order = 2000
def render(self): def render(self):
segment_count = models.Segment.objects.count() segment_count = models.Segment.objects.count()
target_url = reverse('wagtail_personalisation_segment_modeladmin_index') target_url = reverse("wagtail_personalisation_segment_modeladmin_index")
title = _("Segments") title = _("Segments")
return mark_safe(""" return mark_safe(
"""
<li class="icon icon-fa-snowflake-o"> <li class="icon icon-fa-snowflake-o">
<a href="{}"><span>{}</span>{}</a> <a href="{}"><span>{}</span>{}</a>
</li>""".format(target_url, segment_count, title)) </li>""".format(
target_url, segment_count, title
)
)
class PersonalisedPagesSummaryPanel(PagesSummaryItem): class PersonalisedPagesSummaryPanel(PagesSummaryItem):
order = 2100 order = 2100
def render(self): def render(self):
page_count = models.PersonalisablePageMetadata.objects.filter(segment__isnull=True).count() page_count = models.PersonalisablePageMetadata.objects.filter(
segment__isnull=True
).count()
title = _("Personalised Page") title = _("Personalised Page")
return mark_safe(""" return mark_safe(
"""
<li class="icon icon-fa-file-o"> <li class="icon icon-fa-file-o">
<span>{}</span>{}{} <span>{}</span>{}{}
</li>""".format(page_count, title, pluralize(page_count))) </li>""".format(
page_count, title, pluralize(page_count)
)
)
class VariantPagesSummaryPanel(PagesSummaryItem): class VariantPagesSummaryPanel(PagesSummaryItem):
@ -240,15 +275,20 @@ class VariantPagesSummaryPanel(PagesSummaryItem):
def render(self): def render(self):
page_count = models.PersonalisablePageMetadata.objects.filter( page_count = models.PersonalisablePageMetadata.objects.filter(
segment__isnull=False).count() segment__isnull=False
).count()
title = _("Variant") title = _("Variant")
return mark_safe(""" return mark_safe(
"""
<li class="icon icon-fa-files-o"> <li class="icon icon-fa-files-o">
<span>{}</span>{}{} <span>{}</span>{}{}
</li>""".format(page_count, title, pluralize(page_count))) </li>""".format(
page_count, title, pluralize(page_count)
)
)
@hooks.register('construct_homepage_summary_items') @hooks.register("construct_homepage_summary_items")
def add_personalisation_summary_panels(request, items): def add_personalisation_summary_panels(request, items):
"""Adds a summary panel to the Wagtail dashboard showing the total amount """Adds a summary panel to the Wagtail dashboard showing the total amount
of segments on the site and allowing quick access to the Segment of segments on the site and allowing quick access to the Segment
@ -260,20 +300,21 @@ def add_personalisation_summary_panels(request, items):
items.append(VariantPagesSummaryPanel(request)) items.append(VariantPagesSummaryPanel(request))
@hooks.register('before_delete_page') @hooks.register("before_delete_page")
def delete_related_variants(request, page): def delete_related_variants(request, page):
if not isinstance(page, models.PersonalisablePageMixin) \ if (
or not page.personalisation_metadata.is_canonical: not isinstance(page, models.PersonalisablePageMixin)
or not page.personalisation_metadata.is_canonical
):
return return
# Get a list of related personalisation metadata for all the related # Get a list of related personalisation metadata for all the related
# variants. # variants.
variants_metadata = ( variants_metadata = page.personalisation_metadata.variants_metadata.select_related(
page.personalisation_metadata.variants_metadata "variant"
.select_related('variant')
) )
next_url = get_valid_next_url_from_request(request) next_url = get_valid_next_url_from_request(request)
if request.method == 'POST': if request.method == "POST":
parent_id = page.get_parent().id parent_id = page.get_parent().id
with transaction.atomic(): with transaction.atomic():
# To ensure variants are deleted for all descendants, start with # To ensure variants are deleted for all descendants, start with
@ -284,36 +325,36 @@ def delete_related_variants(request, page):
for metadata in PersonalisablePageMetadata.objects.filter( for metadata in PersonalisablePageMetadata.objects.filter(
canonical_page__in=page.get_descendants(inclusive=True), canonical_page__in=page.get_descendants(inclusive=True),
variant=F("canonical_page"), variant=F("canonical_page"),
).order_by('-canonical_page__depth'): ).order_by("-canonical_page__depth"):
for variant_metadata in metadata.variants_metadata.select_related('variant'): for variant_metadata in metadata.variants_metadata.select_related(
"variant"
):
# Call delete() on objects to trigger any signals or hooks. # Call delete() on objects to trigger any signals or hooks.
variant_metadata.variant.delete() variant_metadata.variant.delete()
metadata.delete() metadata.delete()
metadata.canonical_page.delete() metadata.canonical_page.delete()
msg = _("Page '{0}' and its variants deleted.") msg = _("Page '{0}' and its variants deleted.")
messages.success( messages.success(request, msg.format(page.get_admin_display_title()))
request,
msg.format(page.get_admin_display_title())
)
for fn in hooks.get_hooks('after_delete_page'): for fn in hooks.get_hooks("after_delete_page"):
result = fn(request, page) result = fn(request, page)
if hasattr(result, 'status_code'): if hasattr(result, "status_code"):
return result return result
if next_url: if next_url:
return redirect(next_url) return redirect(next_url)
return redirect('wagtailadmin_explore', parent_id) return redirect("wagtailadmin_explore", parent_id)
return render( return render(
request, request,
'wagtailadmin/pages/wagtail_personalisation/confirm_delete.html', { "wagtailadmin/pages/wagtail_personalisation/confirm_delete.html",
'page': page, {
'descendant_count': page.get_descendant_count(), "page": page,
'next': next_url, "descendant_count": page.get_descendant_count(),
'variants': Page.objects.filter( "next": next_url,
pk__in=variants_metadata.values_list('variant_id') "variants": Page.objects.filter(
) pk__in=variants_metadata.values_list("variant_id")
} ),
},
) )

View File

@ -7,6 +7,16 @@ 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:
from wagtail.core.models import Locale
class LocaleFactory(factory.DjangoModelFactory):
language_code = "en"
class Meta:
model = Locale
except ImportError:
pass
class ContentPageFactory(PageFactory): class ContentPageFactory(PageFactory):
parent = None parent = None

View File

@ -23,7 +23,7 @@ def site():
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()
@ -46,6 +46,6 @@ class RequestFactory(BaseRequestFactory):
return request return request
@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')

View File

@ -1,4 +1,5 @@
import os import os
from importlib.util import find_spec
DATABASES = { DATABASES = {
'default': { 'default': {
@ -58,10 +59,14 @@ MIDDLEWARE = (
'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.core.middleware.SiteMiddleware',
) )
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',

View File

@ -76,5 +76,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()
new_panel = test_segment.panels[1].children[0].bind_to_model(Segment) try:
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)
assert new_panel.related.name == "wagtail_personalisation_timerules" assert new_panel.related.name == "wagtail_personalisation_timerules"

View File

@ -2,12 +2,20 @@ 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 ( from tests.factories.page import (ContentPageFactory, PersonalisablePageMetadataFactory)
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
try:
from tests.factories.page import LocaleFactory
locale_factory = True
except ImportError:
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')

View File

@ -106,5 +106,5 @@ def test_segment_delete_view_raises_permission_denied(rf, segmented_page, user):
) )
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.'
with pytest.raises(PermissionDenied, message=message): with pytest.raises(PermissionDenied):
view.delete_instance() view.delete_instance()

42
tox.ini
View File

@ -1,23 +1,30 @@
[tox] [tox]
envlist = py{36}-django{111,20,21,22}-wagtail{20,21,22,23,24,25,26}{,-geoip2},lint envlist =
flake8
py{36,37,38}-dj{22}-wt{211,212,213}
py{37,38}-dj{30,31}-wt{211,212,213}
[gh-actions]
python =
3.6: py36
3.7: py37
3.8: py38
[testenv] [testenv]
basepython = python3.6 basepython =
py36: python3.6
py37: python3.7
py38: python3.8
commands = coverage run --parallel -m pytest -rs {posargs} commands = coverage run --parallel -m pytest -rs {posargs}
extras = test extras = test
deps = deps =
django20: django>=2.0,<2.1 dj22: Django>=2.2.8,<2.3
django21: django>=2.1,<2.2 dj30: Django>=3.0,<3.1
django22: django>=2.2,<2.3 dj31: Django>=3.1,<3.2
wagtail20: wagtail>=2.0,<2.1 wt211: wagtail>=2.11,<2.12
wagtail21: wagtail>=2.1,<2.2 wt212: wagtail>=2.12,<2.13
wagtail22: wagtail>=2.2,<2.3 wt213: wagtail>=2.13,<2.14
wagtail23: wagtail>=2.3,<2.4
wagtail24: wagtail>=2.4,<2.5
wagtail25: wagtail>=2.5,<2.6
wagtail26: wagtail>=2.6,<2.7
geoip2: geoip2 geoip2: geoip2
django111: django>=1.11,<1.12
[testenv:coverage-report] [testenv:coverage-report]
basepython = python3.6 basepython = python3.6
@ -33,3 +40,12 @@ deps = flake8==3.5.0
commands = commands =
flake8 src tests setup.py flake8 src tests setup.py
isort -q --recursive --diff src/ tests/ isort -q --recursive --diff src/ tests/
[testenv:format]
basepython = python3.8
deps =
isort
black
skip_install = true
commands =
black --check setup.py src/wagtail_personalisation/ tests/

View File

@ -307,9 +307,9 @@ async-throttle@^1.1.0:
integrity sha1-Ip5/P6eip5fobzYOYwmggiTU+no= integrity sha1-Ip5/P6eip5fobzYOYwmggiTU+no=
async@^2.1.2: async@^2.1.2:
version "2.6.3" version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
dependencies: dependencies:
lodash "^4.17.14" lodash "^4.17.14"
@ -4953,9 +4953,9 @@ lodash.uniq@^4.5.0:
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.11: lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.11:
version "4.17.15" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
logalot@^2.0.0: logalot@^2.0.0:
version "2.1.0" version "2.1.0"