diff --git a/.gitignore b/.gitignore index 84bbba5..6c32372 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ .vscode/ build/ +ve/ dist/ htmlcov/ docs/_build diff --git a/.travis.yml b/.travis.yml index 7d20c4a..69b1841 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,8 @@ matrix: env: TOXENV=py36-django20-wagtail21 - python: 3.6 env: TOXENV=py36-django20-wagtail21-geoip2 + - python: 3.6 + env: TOXENV=py36-django111-wagtail22 install: - pip install tox codecov diff --git a/CHANGES b/CHANGES index 11c81de..a9a971a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,10 +1,18 @@ +0.13.0 +================= + - Merged Praekolt fork + - Add custom javascript to segment forms + - bugfix:exclude variant returns queryset when params is queryset + - Added RulePanel, a subclass of InlinePanel, for Rules + - Upgrade to Wagtail > 2.0, drop support for Wagtail < 2 + 0.12.0 ================== - Fix Django version classifier in setup.py 0.12.0 ================== - - Merged forks of Torchbox and Praekelt + - Merged forks of Torchbox and Praekolt - Wagtail 2 compatibility - Makefile adjustments for portability - Adds simple segment forcing for superusers diff --git a/VERSON b/VERSON new file mode 100644 index 0000000..21e8796 --- /dev/null +++ b/VERSON @@ -0,0 +1 @@ +1.0.3 diff --git a/docs/conf.py b/docs/conf.py index b87f0a4..e4c566d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,7 @@ master_doc = 'index' # General information about the project. project = 'wagtail-personalisation' -copyright = '2018, Lab Digital BV' +copyright = '2019, Lab Digital BV' author = 'Lab Digital BV' # The version info for the project you're documenting, acts as replacement for diff --git a/setup.py b/setup.py index e082272..1581033 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ import re from setuptools import find_packages, setup install_requires = [ - 'wagtail>=2.0', + 'wagtail>=2.0,<2.3', 'user-agents>=1.1.0', 'wagtailfontawesome>=1.1.3', 'pycountry', @@ -20,7 +20,7 @@ tests_require = [ 'pytest-pythonpath==0.7.2', 'pytest-sugar==0.9.1', 'pytest==3.4.2', - 'wagtail_factories==1.0.0', + 'wagtail_factories==1.1.0', 'pytest-mock==1.6.3', ] @@ -52,7 +52,7 @@ setup( license='MIT', long_description=long_description, classifiers=[ - 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', diff --git a/src/wagtail_personalisation/models.py b/src/wagtail_personalisation/models.py index 124a0f1..1e167f4 100644 --- a/src/wagtail_personalisation/models.py +++ b/src/wagtail_personalisation/models.py @@ -1,16 +1,15 @@ import random +import wagtail from django import forms from django.conf import settings from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models, transaction from django.template.defaultfilters import slugify -from django.utils.encoding import python_2_unicode_compatible from django.utils.functional import cached_property from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from modelcluster.models import ClusterableModel -import wagtail from wagtail.admin.edit_handlers import ( FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel) from wagtail.core.models import Page @@ -21,12 +20,19 @@ from wagtail_personalisation.utils import count_active_days from .forms import SegmentAdminForm +class RulePanel(InlinePanel): + def on_model_bound(self): + self.db_field = self.model._meta.get_field( + self.relation_name.replace('_related', 's')) + manager = getattr(self.model, self.relation_name) + self.related = manager.rel + + class SegmentQuerySet(models.QuerySet): def enabled(self): return self.filter(status=self.model.STATUS_ENABLED) -@python_2_unicode_compatible class Segment(ClusterableModel): """The segment model.""" STATUS_ENABLED = 'enabled' @@ -121,8 +127,8 @@ class Segment(ClusterableModel): FieldPanel('randomisation_percent', classname='percent_field'), ], heading="Segment"), MultiFieldPanel([ - InlinePanel( - "{}s".format(rule_model._meta.db_table), + RulePanel( + "{}_related".format(rule_model._meta.db_table), label='{}{}'.format( rule_model._meta.verbose_name, ' ({})'.format(_('Static compatible')) if rule_model.static else '' diff --git a/src/wagtail_personalisation/views.py b/src/wagtail_personalisation/views.py index de421a4..0765230 100644 --- a/src/wagtail_personalisation/views.py +++ b/src/wagtail_personalisation/views.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import csv from django import forms @@ -87,7 +85,10 @@ class SegmentModelAdmin(ModelAdmin): 'page_count', 'variant_count', 'statistics') 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'] + 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): diff --git a/src/wagtail_personalisation/wagtail_hooks.py b/src/wagtail_personalisation/wagtail_hooks.py index a65e57c..24aecca 100644 --- a/src/wagtail_personalisation/wagtail_hooks.py +++ b/src/wagtail_personalisation/wagtail_hooks.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import logging from django.conf.urls import include, url @@ -227,8 +225,7 @@ class PersonalisedPagesSummaryPanel(PagesSummaryItem): order = 2100 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") return mark_safe("""
  • diff --git a/tests/conftest.py b/tests/conftest.py index 77e07e7..e677aeb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import pytest pytest_plugins = [ @@ -7,6 +5,11 @@ pytest_plugins = [ ] +@pytest.fixture(autouse=True) +def enable_db_access(db): + pass + + @pytest.fixture(scope='session') def django_db_setup(django_db_setup, django_db_blocker): from wagtail.core.models import Page, Site diff --git a/tests/settings.py b/tests/settings.py index 51125a7..1c2d0ee 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,6 +1,7 @@ -from __future__ import absolute_import, unicode_literals - import os +from distutils.version import StrictVersion as V + +import django DATABASES = { 'default': { @@ -52,16 +53,27 @@ TEMPLATES = [ }, ] -MIDDLEWARE = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'wagtail.core.middleware.SiteMiddleware', -) +def get_middleware_settings(): + return ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + 'wagtail.core.middleware.SiteMiddleware', + ) + + +# Django 1.10 started to use "MIDDLEWARE" instead of "MIDDLEWARE_CLASSES". +if V(django.get_version()) < V('1.10'): + MIDDLEWARE_CLASSES = get_middleware_settings() +else: + MIDDLEWARE = get_middleware_settings() + INSTALLED_APPS = ( 'wagtail_personalisation', diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index d814f7b..0a7cd27 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - import datetime import pytest @@ -8,7 +6,7 @@ from django.db.models import ProtectedError from tests.factories.page import ContentPageFactory from tests.factories.segment import SegmentFactory from tests.site.pages import models -from wagtail_personalisation.models import PersonalisablePageMetadata +from wagtail_personalisation.models import PersonalisablePageMetadata, Segment from wagtail_personalisation.rules import TimeRule @@ -73,3 +71,10 @@ def test_sitemap_generation_for_canonical_pages_is_enabled(segmented_page): def test_sitemap_generation_for_variants_is_disabled(segmented_page): assert not segmented_page.personalisation_metadata.is_canonical assert not segmented_page.get_sitemap_urls() + + +@pytest.mark.django_db +def test_segment_edit_view(site, client, django_user_model): + test_segment = Segment() + new_panel = test_segment.panels[1].children[0].bind_to_model(Segment) + assert new_panel.related.name == "wagtail_personalisation_timerules" diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 1d0c182..f34704f 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3,8 +3,14 @@ import pytest from django.test import override_settings from tests.factories.page import ContentPageFactory +from wagtail.core.models import Page as WagtailPage from wagtail_personalisation.utils import ( - can_delete_pages, get_client_ip, impersonate_other_page) + can_delete_pages, get_client_ip, impersonate_other_page, exclude_variants) + + +class Metadata: + def __init__(self, is_canonical=True): + self.is_canonical = is_canonical @pytest.fixture @@ -64,3 +70,54 @@ def test_get_client_ip_custom_get_client_ip_function_does_not_exist(rf): ) def test_get_client_ip_custom_get_client_ip_used(rf): assert get_client_ip(rf.get('/')) == '123.123.123.123' + + +def test_exclude_variants_with_pages_querysets(): + ''' + Test that excludes variant works for querysets + ''' + for i in range(5): + page = WagtailPage(path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i)) + page.save() + pages = WagtailPage.objects.all().order_by('id') + + result = exclude_variants(pages) + assert type(result) == type(pages) + assert result == pages + + +def test_exclude_variants_with_pages_querysets_not_canonical(): + ''' + Test that excludes variant works for querysets with + personalisation_metadata canonical False + ''' + for i in range(5): + page = WagtailPage(path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i)) + page.save() + pages = WagtailPage.objects.all().order_by('id') + # add variants + for page in pages: + page.personalisation_metadata = Metadata(is_canonical=False) + page.save() + + result = exclude_variants(pages) + assert type(result) == type(pages) + assert result.count() == 0 + + +def test_exclude_variants_with_pages_querysets_meta_none(): + ''' + Test that excludes variant works for querysets with meta as none + ''' + for i in range(5): + page = WagtailPage(path="/" + str(i), depth=0, url_path="/", title="Hoi " + str(i)) + page.save() + pages = WagtailPage.objects.all().order_by('id') + # add variants + for page in pages: + page.personalisation_metadata = None + page.save() + + result = exclude_variants(pages) + assert type(result) == type(pages) + assert result == pages diff --git a/tox.ini b/tox.ini index 159f784..a3c0211 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{36}-django{20}-wagtail{20,21}{,-geoip2},lint +envlist = py{36}-django{111,20}-wagtail{20,21,22}{,-geoip2},lint [testenv] basepython = python3.6 @@ -9,7 +9,9 @@ deps = django20: django>=2.0,<2.1 wagtail20: wagtail>=2.0,<2.1 wagtail21: wagtail>=2.1,<2.2 + wagtail22: wagtail>=2.2,<2.3 geoip2: geoip2 + django111: django>=1.11,<1.12 [testenv:coverage-report] basepython = python3.6 @@ -21,7 +23,7 @@ commands = [testenv:lint] basepython = python3.6 -deps = flake8 +deps = flake8==3.5.0 commands = flake8 src tests setup.py isort -q --recursive --diff src/ tests/