Compare commits
6 Commits
0.10.1
...
feature/da
Author | SHA1 | Date | |
---|---|---|---|
|
985c9cbb95 | ||
|
ad79fa3b2f | ||
|
389dc243e1 | ||
|
47bfa384f3 | ||
|
83c2a4289e | ||
|
9b25cd2a94 |
34
.travis.yml
34
.travis.yml
@@ -7,6 +7,30 @@ matrix:
|
||||
# Django 1.9, Wagtail 1.9
|
||||
- python: 2.7
|
||||
env: TOXENV=py27-django19-wagtail19
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django19-wagtail19
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django19-wagtail19
|
||||
|
||||
# Django 1.10, Wagtail 1.10
|
||||
- python: 2.7
|
||||
env: TOXENV=py27-django110-wagtail110
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django110-wagtail110
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django110-wagtail110
|
||||
|
||||
# Django 1.11, Wagtail 1.10
|
||||
- python: 2.7
|
||||
env: TOXENV=py27-django111-wagtail110
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django111-wagtail110
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django111-wagtail110
|
||||
|
||||
allow_failures:
|
||||
- python: 3.5
|
||||
env: TOXENV=lint
|
||||
|
||||
install:
|
||||
- pip install tox codecov
|
||||
@@ -17,13 +41,3 @@ script:
|
||||
after_success:
|
||||
- tox -e coverage-report
|
||||
- codecov
|
||||
|
||||
deploy:
|
||||
provider: pypi
|
||||
distributions: sdist bdist_wheel
|
||||
user: praekelt.org
|
||||
password:
|
||||
secure: IxPnc95OFNQsl7kFfLlLc38KfHh/W79VXnhEkdb2x1GZznOsG167QlhpAnyXIJysCQxgMMwLMuPOOdk1WIxOpGNM1/M80hNzPAfxMYWPuwposDdwoIc8SyREPJt16BXWAY+rAH8SHxld9p1YdRbOEPSSglODe4dCmQWsCbKjV3aKv+gZxBvf6pMxUglp2fBIlCwrE77MyMYh9iW0AGzD6atC2xN9NgAm4f+2WFlKCUL/MztLhNWdvWEiibQav6Tts7p8tWrsBVzywDW2IOy3O0ihPgRdISZ7QrxhiJTjlHYPAy4eRGOnYSQXvp6Dy8ONE5a6Uv5g3Xw2UmQo85sSMUs2VxR0G7d+PQgL0A7ENBQ5i7eSAFHYs8EswiGilW2A7mM4qRXwg9URLelYSdkM+aNXvR+25dCsXakiO4NjCz/BPgNzxPeQLlBdxR5vHugeM/XYuhy6CHlZrR/uueaO0X8RyhJcqeDjBy58IrwYS3Mpj7QCfBpQ9PqsqXEWV9BKwKiBXM2+3hkhawFDWa0GW2PDbORKtSLy/ORfGLx5Y9qxQYKEGvFQA3iqkTjrLj+KeUziKtuvEAcvsdBIJVIxeoHwdl+qqxEm8A7YuRBnWVxWc3jE6wBXboeFP92gVe/ueoXmY22riK9Ja0pli3TyNga8by9WM8Qp4D2ZqkVXHwg=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
|
4
CHANGES
4
CHANGES
@@ -1,7 +1,3 @@
|
||||
0.10.0
|
||||
==================
|
||||
- Adds static and dynamic segments
|
||||
|
||||
0.9.1 (tbd)
|
||||
==================
|
||||
|
||||
|
@@ -1,6 +1,3 @@
|
||||
include README.rst
|
||||
|
||||
recursive-include src *
|
||||
|
||||
recursive-exclude src __pycache__
|
||||
recursive-exclude src *.py[co]
|
||||
recursive-include src
|
10
README.rst
10
README.rst
@@ -14,6 +14,11 @@
|
||||
|
||||
.. end-no-pypi
|
||||
|
||||
.. image:: logo.png
|
||||
:scale: 50 %
|
||||
:alt: Wagxperience
|
||||
:align: center
|
||||
|
||||
Wagtail Personalisation
|
||||
=======================
|
||||
|
||||
@@ -24,11 +29,6 @@ in the admin interface.
|
||||
|
||||
.. _Wagtail CMS: http://wagtail.io/
|
||||
|
||||
.. image:: logo.png
|
||||
:scale: 50 %
|
||||
:alt: Wagxperience
|
||||
:align: center
|
||||
|
||||
|
||||
.. image:: screenshot.png
|
||||
|
||||
|
@@ -55,10 +55,10 @@ author = 'Lab Digital BV'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.10.1'
|
||||
version = '0.9.1'
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.10.1'
|
||||
release = '0.9.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@@ -1,132 +1,199 @@
|
||||
.nice-padding {
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
}
|
||||
$color-gray: #D0D2D3;
|
||||
$color-light-gray: #EBEBEB;
|
||||
$color-lightest-gray: #F6F6F6;
|
||||
$color-dark-gray: #727272;
|
||||
$block-spacing: 20px;
|
||||
|
||||
.block_container {
|
||||
display: block;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.block_container .block {
|
||||
display: block;
|
||||
float: left;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: calc(50% - 10px);
|
||||
min-height: 216px;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 3px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.00), 0 1px 2px rgba(0,0,0,0.00);
|
||||
transition: box-shadow 0.3s cubic-bezier(.25,.8,.25,1), border 0.3s cubic-bezier(.25,.8,.25,1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.block_container .block--disabled h2,
|
||||
.block_container .block--disabled .inspect_container {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.block_container .block h2 {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.block_container .block:nth-child(odd) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.block_container .block .block_actions {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.block_container .block .block_actions li {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.block_container .block .block_actions li:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.block_container .block.suggestion {
|
||||
border: 1px dashed #d9d9d9;
|
||||
}
|
||||
|
||||
.block_container .block:hover {
|
||||
border: 1px solid #fff;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
|
||||
@media (max-width: 699px) {
|
||||
.block_container .block {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.block_container .block .inspect_container {
|
||||
.block-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
|
||||
.block_container .block .inspect_container .inspect {
|
||||
display: block;
|
||||
float: left;
|
||||
width: calc(50% - 10px);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
.stat_card {
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.block_container .block span.icon::before {
|
||||
margin-right: 0.3em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.block_container .block .inspect_container .inspect li span {
|
||||
.block {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 5px 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.block_container .block .inspect_container .inspect li pre {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
margin: 5px 0 5px 21px;
|
||||
padding: 2px 5px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
overflow-x: hidden;
|
||||
width: 22%;
|
||||
max-width: 320px;
|
||||
min-width: 280px;
|
||||
min-height: 260px;
|
||||
margin: 1.5%;
|
||||
border: 1px solid $color-gray;
|
||||
border-radius: 3px;
|
||||
padding-bottom: 62px;
|
||||
|
||||
&__segment {
|
||||
position: relative;
|
||||
|
||||
.block-header,
|
||||
.block-rules,
|
||||
.block-footer {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.block-header {
|
||||
padding: $block-spacing;
|
||||
border-bottom: 1px solid $color-gray;
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.block-rules {
|
||||
padding: $block-spacing 0;
|
||||
}
|
||||
|
||||
.block-footer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
padding: $block-spacing / 2 $block-spacing;
|
||||
border-top: 1px solid $color-gray;
|
||||
background-color: $color-lightest-gray;
|
||||
}
|
||||
|
||||
.block-data {
|
||||
margin-bottom: $block-spacing;
|
||||
margin-right: $block-spacing / 2;
|
||||
|
||||
&__description {
|
||||
margin-bottom: 3px;
|
||||
font-size: 14px;
|
||||
color: $color-dark-gray;
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-bottom: 0;
|
||||
font-size: 18px;
|
||||
|
||||
&--code {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
padding-left: 3px !important;
|
||||
padding-right: 3px;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
margin-left: $block-spacing + 10px;
|
||||
margin-top: 0;
|
||||
border: 1px solid $color-gray;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
background-color: $color-lightest-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&--icon {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translate(-20%, -50%);
|
||||
margin-right: 0;
|
||||
font-size: 32px;
|
||||
color: $color-light-gray;
|
||||
}
|
||||
|
||||
.block-data__description,
|
||||
.block-data__content {
|
||||
padding-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.block-stats {
|
||||
&__content {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.block-actions {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
&__action {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&-suggestion {
|
||||
position: relative;
|
||||
border: 1px dashed $color-gray;
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.block-suggestion {
|
||||
&__text {
|
||||
color: $color-dark-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.block_container .block.suggestion .suggestive_text {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: calc(100% - 40px);
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #d9d9d9;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
.segment {
|
||||
&--match-all,
|
||||
&--match-any {
|
||||
.block-data--icon {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
padding-left: 5px;
|
||||
border-left: 1px solid $color-light-gray;
|
||||
color: $color-light-gray;
|
||||
text-align: left;
|
||||
font-family: "FontAwesome";
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--match-all {
|
||||
.block-data--icon {
|
||||
&:after {
|
||||
content: "\f0c1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--match-any {
|
||||
.block-data--icon {
|
||||
&:after {
|
||||
content: "\f127";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.10.1
|
||||
current_version = 0.9.1
|
||||
commit = true
|
||||
tag = true
|
||||
tag_name = {new_version}
|
||||
@@ -15,14 +15,14 @@ python_paths = .
|
||||
[flake8]
|
||||
ignore = E731
|
||||
max-line-length = 120
|
||||
exclude =
|
||||
exclude =
|
||||
src/**/migrations/*.py
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[coverage:run]
|
||||
omit =
|
||||
omit =
|
||||
src/**/migrations/*.py
|
||||
|
||||
[bumpversion:file:setup.py]
|
||||
|
17
setup.py
17
setup.py
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
|
||||
install_requires = [
|
||||
'wagtail>=1.9,<1.11',
|
||||
'user-agents>=1.0.1',
|
||||
@@ -9,15 +10,15 @@ install_requires = [
|
||||
|
||||
tests_require = [
|
||||
'factory_boy==2.8.1',
|
||||
'flake8',
|
||||
'flake8-blind-except',
|
||||
'flake8-debugger',
|
||||
'flake8-imports',
|
||||
'flake8',
|
||||
'freezegun==0.3.8',
|
||||
'pytest-cov==2.4.0',
|
||||
'pytest-django==3.1.2',
|
||||
'pytest-pythonpath==0.7.2',
|
||||
'pytest-sugar==0.7.1',
|
||||
'pytest-mock==1.6.3',
|
||||
'pytest==3.1.0',
|
||||
'wagtail_factories==0.3.0',
|
||||
]
|
||||
@@ -31,12 +32,12 @@ with open('README.rst') as fh:
|
||||
'^.. start-no-pypi.*^.. end-no-pypi', '', fh.read(), flags=re.M | re.S)
|
||||
|
||||
setup(
|
||||
name='wagtail-personalisation-molo',
|
||||
version='0.10.1',
|
||||
description='A forked version of Wagtail add-on for showing personalized content',
|
||||
author='Praekelt.org',
|
||||
author_email='dev@praekeltfoundation.org',
|
||||
url='https://github.com/praekeltfoundation/wagtail-personalisation/',
|
||||
name='wagtail-personalisation',
|
||||
version='0.9.1',
|
||||
description='A Wagtail add-on for showing personalized content',
|
||||
author='Lab Digital BV',
|
||||
author_email='opensource@labdigital.nl',
|
||||
url='http://labdigital.nl',
|
||||
install_requires=install_requires,
|
||||
tests_require=tests_require,
|
||||
extras_require={
|
||||
|
@@ -3,7 +3,6 @@ from __future__ import absolute_import, unicode_literals
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils import timezone
|
||||
|
||||
from wagtail_personalisation.models import Segment
|
||||
from wagtail_personalisation.rules import AbstractBaseRule
|
||||
@@ -144,7 +143,7 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
||||
|
||||
def get_visit_count(self, page=None):
|
||||
"""Return the number of visits on the current request or given page"""
|
||||
path = page.url_path if page else self.request.path
|
||||
path = page.path if page else self.request.path
|
||||
visit_count = self.request.session.setdefault('visit_count', [])
|
||||
for visit in visit_count:
|
||||
if visit['path'] == path:
|
||||
@@ -175,22 +174,15 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
||||
# Run tests on all remaining enabled segments to verify applicability.
|
||||
additional_segments = []
|
||||
for segment in enabled_segments:
|
||||
if segment.is_static and segment.static_users.filter(id=self.request.user.id).exists():
|
||||
segment_rules = []
|
||||
for rule_model in rule_models:
|
||||
segment_rules.extend(rule_model.objects.filter(segment=segment))
|
||||
|
||||
result = self._test_rules(segment_rules, self.request,
|
||||
match_any=segment.match_any)
|
||||
|
||||
if result:
|
||||
additional_segments.append(segment)
|
||||
elif not segment.is_static or not segment.is_full:
|
||||
segment_rules = []
|
||||
for rule_model in rule_models:
|
||||
segment_rules.extend(rule_model.objects.filter(segment=segment))
|
||||
|
||||
result = self._test_rules(segment_rules, self.request,
|
||||
match_any=segment.match_any)
|
||||
|
||||
if result and segment.is_static and not segment.is_full:
|
||||
if self.request.user.is_authenticated():
|
||||
segment.static_users.add(self.request.user)
|
||||
|
||||
if result:
|
||||
additional_segments.append(segment)
|
||||
|
||||
self.set_segments(current_segments + additional_segments)
|
||||
self.update_visit_count()
|
||||
|
@@ -1,107 +0,0 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from importlib import import_module
|
||||
from itertools import takewhile
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.sessions.models import Session
|
||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils import timezone
|
||||
from django.utils.lru_cache import lru_cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from wagtail.wagtailadmin.forms import WagtailAdminModelForm
|
||||
|
||||
|
||||
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
@lru_cache(maxsize=1000)
|
||||
def user_from_data(user_id):
|
||||
User = get_user_model()
|
||||
try:
|
||||
return User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
return AnonymousUser()
|
||||
|
||||
|
||||
|
||||
class SegmentAdminForm(WagtailAdminModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super(SegmentAdminForm, self).clean()
|
||||
Segment = self._meta.model
|
||||
|
||||
rules = [
|
||||
form.instance for formset in self.formsets.values()
|
||||
for form in formset
|
||||
if form not in formset.deleted_forms
|
||||
]
|
||||
consistent = rules and Segment.all_static(rules)
|
||||
|
||||
if 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.has_changed():
|
||||
self.add_error_to_fields(self, excluded=['name', 'enabled'])
|
||||
|
||||
for formset in self.formsets.values():
|
||||
if formset.has_changed():
|
||||
for form in formset:
|
||||
if form not in formset.deleted_forms:
|
||||
self.add_error_to_fields(form)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def add_error_to_fields(self, form, excluded=list()):
|
||||
for field in form.changed_data:
|
||||
if field not in excluded:
|
||||
form.add_error(field, _('Cannot update a static segment'))
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
is_new = not self.instance.id
|
||||
|
||||
if not self.instance.is_static:
|
||||
self.instance.count = 0
|
||||
|
||||
instance = super(SegmentAdminForm, self).save(*args, **kwargs)
|
||||
|
||||
if is_new and instance.is_static and instance.all_rules_static:
|
||||
from .adapters import get_segment_adapter
|
||||
|
||||
request = RequestFactory().get('/')
|
||||
request.session = SessionStore()
|
||||
adapter = get_segment_adapter(request)
|
||||
|
||||
users_to_add = []
|
||||
sessions = Session.objects.iterator()
|
||||
take_session = takewhile(
|
||||
lambda x: instance.count == 0 or len(users_to_add) <= instance.count,
|
||||
sessions
|
||||
)
|
||||
for session in take_session:
|
||||
session_data = session.get_decoded()
|
||||
user = user_from_data(session_data.get('_auth_user_id'))
|
||||
if user.is_authenticated():
|
||||
request.user = user
|
||||
request.session = SessionStore(session_key=session.session_key)
|
||||
passes = adapter._test_rules(instance.get_rules(), request, instance.match_any)
|
||||
if passes:
|
||||
users_to_add.append(user)
|
||||
|
||||
instance.static_users.add(*users_to_add)
|
||||
|
||||
return instance
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
media = super(SegmentAdminForm, self).media
|
||||
media.add_js(
|
||||
[static('js/segment_form_control.js')]
|
||||
)
|
||||
return media
|
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.6 on 2017-10-17 11:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sessions', '0001_initial'),
|
||||
('wagtail_personalisation', '0012_remove_personalisablepagemetadata_is_segmented'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='segment',
|
||||
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.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='segment',
|
||||
name='sessions',
|
||||
field=models.ManyToManyField(to='sessions.Session'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='segment',
|
||||
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),
|
||||
),
|
||||
]
|
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.6 on 2017-11-01 15:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('wagtail_personalisation', '0013_add_dynamic_static_to_segment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='segment',
|
||||
name='sessions',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='segment',
|
||||
name='static_users',
|
||||
field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
@@ -1,28 +1,18 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.models import Session
|
||||
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
|
||||
from wagtail.wagtailadmin.edit_handlers import (
|
||||
FieldPanel,
|
||||
FieldRowPanel,
|
||||
InlinePanel,
|
||||
MultiFieldPanel,
|
||||
)
|
||||
FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel)
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
from wagtail_personalisation.rules import AbstractBaseRule
|
||||
from wagtail_personalisation.utils import count_active_days
|
||||
|
||||
from .forms import SegmentAdminForm
|
||||
|
||||
|
||||
class SegmentQuerySet(models.QuerySet):
|
||||
def enabled(self):
|
||||
@@ -40,14 +30,6 @@ class Segment(ClusterableModel):
|
||||
(STATUS_DISABLED, _('Disabled')),
|
||||
)
|
||||
|
||||
TYPE_DYNAMIC = 'dynamic'
|
||||
TYPE_STATIC = 'static'
|
||||
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_DYNAMIC, _('Dynamic')),
|
||||
(TYPE_STATIC, _('Static')),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
create_date = models.DateTimeField(auto_now_add=True)
|
||||
edit_date = models.DateTimeField(auto_now=True)
|
||||
@@ -62,35 +44,9 @@ class Segment(ClusterableModel):
|
||||
default=False,
|
||||
help_text=_("Should the segment match all the rules or just one of them?")
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=20,
|
||||
choices=TYPE_CHOICES,
|
||||
default=TYPE_DYNAMIC,
|
||||
help_text=mark_safe(_("""
|
||||
</br></br><strong>Dynamic:</strong> Users in this segment will change
|
||||
as more or less meet the rules specified in the segment.
|
||||
</br><strong>Static:</strong> If the segment contains only static
|
||||
compatible rules the segment will contain the members that pass
|
||||
those rules when the segment is created. Mixed static segments or
|
||||
those containing entirely non static compatible rules will be
|
||||
populated using the count variable.
|
||||
"""))
|
||||
)
|
||||
count = 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."
|
||||
)
|
||||
)
|
||||
static_users = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
)
|
||||
|
||||
objects = SegmentQuerySet.as_manager()
|
||||
|
||||
base_form_class = SegmentAdminForm
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Segment.panels = [
|
||||
MultiFieldPanel([
|
||||
@@ -100,16 +56,11 @@ class Segment(ClusterableModel):
|
||||
FieldPanel('persistent'),
|
||||
]),
|
||||
FieldPanel('match_any'),
|
||||
FieldPanel('type', widget=forms.RadioSelect),
|
||||
FieldPanel('count', classname='count_field'),
|
||||
], heading="Segment"),
|
||||
MultiFieldPanel([
|
||||
InlinePanel(
|
||||
"{}_related".format(rule_model._meta.db_table),
|
||||
label='{}{}'.format(
|
||||
rule_model._meta.verbose_name,
|
||||
' ({})'.format(_('Static compatible')) if rule_model.static else ''
|
||||
),
|
||||
label=rule_model._meta.verbose_name,
|
||||
) for rule_model in AbstractBaseRule.__subclasses__()
|
||||
], heading=_("Rules")),
|
||||
]
|
||||
@@ -119,23 +70,6 @@ class Segment(ClusterableModel):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def is_static(self):
|
||||
return self.type == self.TYPE_STATIC
|
||||
|
||||
@classmethod
|
||||
def all_static(cls, rules):
|
||||
return all(rule.static for rule in rules)
|
||||
|
||||
@property
|
||||
def all_rules_static(self):
|
||||
rules = self.get_rules()
|
||||
return rules and self.all_static(rules)
|
||||
|
||||
@property
|
||||
def is_full(self):
|
||||
return self.static_users.count() >= self.count
|
||||
|
||||
def encoded_name(self):
|
||||
"""Return a string with a slug for the segment."""
|
||||
return slugify(self.name.lower())
|
||||
|
@@ -18,7 +18,6 @@ from wagtail.wagtailadmin.edit_handlers import (
|
||||
class AbstractBaseRule(models.Model):
|
||||
"""Base for creating rules to segment users with."""
|
||||
icon = 'fa-circle-o'
|
||||
static = False
|
||||
|
||||
segment = ParentalKey(
|
||||
'wagtail_personalisation.Segment',
|
||||
@@ -191,7 +190,6 @@ class VisitCountRule(AbstractBaseRule):
|
||||
|
||||
"""
|
||||
icon = 'fa-calculator'
|
||||
static = True
|
||||
|
||||
OPERATOR_CHOICES = (
|
||||
('more_than', _("More than")),
|
||||
@@ -229,7 +227,7 @@ class VisitCountRule(AbstractBaseRule):
|
||||
|
||||
adapter = get_segment_adapter(request)
|
||||
|
||||
visit_count = adapter.get_visit_count(self.counted_page)
|
||||
visit_count = adapter.get_visit_count()
|
||||
if visit_count and operator == "more_than":
|
||||
if visit_count > segment_count:
|
||||
return True
|
||||
|
@@ -1,2 +1,2 @@
|
||||
.nice-padding{padding-left:50px;padding-right:50px}.block_container{display:block;margin-top:30px}.block_container .block{display:block;float:left;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;width:calc(50% - 10px);min-height:216px;padding:10px 20px;margin-bottom:20px;border:1px solid #d9d9d9;border-radius:3px;background-color:#fff;-webkit-box-shadow:0 1px 3px transparent,0 1px 2px transparent;box-shadow:0 1px 3px transparent,0 1px 2px transparent;-webkit-transition:border .3s cubic-bezier(.25,.8,.25,1),-webkit-box-shadow .3s cubic-bezier(.25,.8,.25,1);transition:border .3s cubic-bezier(.25,.8,.25,1),-webkit-box-shadow .3s cubic-bezier(.25,.8,.25,1);transition:box-shadow .3s cubic-bezier(.25,.8,.25,1),border .3s cubic-bezier(.25,.8,.25,1);transition:box-shadow .3s cubic-bezier(.25,.8,.25,1),border .3s cubic-bezier(.25,.8,.25,1),-webkit-box-shadow .3s cubic-bezier(.25,.8,.25,1);cursor:pointer}.block_container .block--disabled .inspect_container,.block_container .block--disabled h2{opacity:.5}.block_container .block h2{display:inline-block;width:auto}.block_container .block:nth-child(odd){margin-right:20px}.block_container .block .block_actions{list-style:none;margin:0;padding:0}.block_container .block .block_actions li{float:left;margin-right:10px}.block_container .block .block_actions li:last-child{margin-right:0}.block_container .block.suggestion{border:1px dashed #d9d9d9}.block_container .block:hover{border:1px solid #fff;-webkit-box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}@media (max-width:699px){.block_container .block{width:100%;margin-right:0}}.block_container .block .inspect_container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;margin-bottom:10px}.block_container .block .inspect_container .inspect{display:block;float:left;width:calc(50% - 10px);padding:0;margin:0;list-style:none}.block_container .block .inspect_container .inspect .stat_card{display:inline-block;margin-bottom:5px;margin-right:10px}.block_container .block span.icon:before{margin-right:.3em;vertical-align:bottom}.block_container .block .inspect_container .inspect li span{display:block;font-size:20px;font-weight:700;margin:5px 0;overflow-wrap:break-word}.block_container .block .inspect_container .inspect li pre{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:auto;background-color:#eee;border:1px solid #ccc;margin:5px 0 5px 21px;padding:2px 5px;word-wrap:break-word;word-break:break-all;border-radius:3px}.block_container .block.suggestion .suggestive_text{display:block;position:absolute;width:calc(100% - 40px);text-align:center;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);color:#d9d9d9;font-size:20px;font-weight:100}
|
||||
.block-container{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.block-container .block{display:block;-webkit-box-sizing:border-box;box-sizing:border-box;overflow-x:hidden;width:22%;max-width:320px;min-width:280px;min-height:260px;margin:1.5%;border:1px solid #d0d2d3;border-radius:3px;padding-bottom:62px}.block-container .block__segment{position:relative}.block-container .block__segment .block-footer,.block-container .block__segment .block-header,.block-container .block__segment .block-rules{display:block;-webkit-box-sizing:border-box;box-sizing:border-box}.block-container .block__segment .block-header{padding:20px;border-bottom:1px solid #d0d2d3}.block-container .block__segment .block-header h2{margin-top:0}.block-container .block__segment .block-rules{padding:20px 0}.block-container .block__segment .block-footer{position:absolute;width:100%;bottom:0;padding:10px 20px;border-top:1px solid #d0d2d3;background-color:#f6f6f6}.block-container .block__segment .block-data{margin-bottom:20px;margin-right:10px}.block-container .block__segment .block-data__description{margin-bottom:3px;font-size:14px;color:#727272}.block-container .block__segment .block-data__content{margin-bottom:0;font-size:18px}.block-container .block__segment .block-data__content--code{display:inline-block;width:auto;padding-left:3px!important;padding-right:3px;padding-top:1px;padding-bottom:1px;margin-left:30px;margin-top:0;border:1px solid #d0d2d3;border-radius:3px;font-size:14px;background-color:#f6f6f6}.block-container .block__segment .block-data--icon{position:relative}.block-container .block__segment .block-data--icon:before{display:block;position:absolute;top:50%;left:0;-webkit-transform:translate(-20%,-50%);transform:translate(-20%,-50%);margin-right:0;font-size:32px;color:#ebebeb}.block-container .block__segment .block-data--icon .block-data__content,.block-container .block__segment .block-data--icon .block-data__description{padding-left:30px}.block-container .block__segment .block-data:last-child{margin-bottom:0}.block-container .block__segment .block-stats__content{margin-bottom:3px}.block-container .block__segment .block-actions{margin:0;padding:0;list-style:none}.block-container .block__segment .block-actions__action{display:inline-block;margin-right:5px}.block-container .block__segment .block-actions__action:last-child{margin-right:0}.block-container .block-suggestion{position:relative;border:1px dashed #d0d2d3}.block-container .block-suggestion__text{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);width:100%;text-align:center;font-size:16px;color:#d0d2d3}.block-container .block-suggestion:hover .block-suggestion__text{color:#727272}.block-container .segment--match-all .block-data--icon,.block-container .segment--match-any .block-data--icon{position:relative}.block-container .segment--match-all .block-data--icon:after,.block-container .segment--match-any .block-data--icon:after{position:absolute;left:5px;padding-left:5px;border-left:1px solid #ebebeb;color:#ebebeb;text-align:left;font-family:FontAwesome;font-size:16px;font-weight:500}.block-container .segment--match-all .block-data--icon:last-child:after,.block-container .segment--match-any .block-data--icon:last-child:after{display:none}.block-container .segment--match-all .block-data--icon:after{content:"\F0C1"}.block-container .segment--match-any .block-data--icon:after{content:"\F127"}
|
||||
/*# sourceMappingURL=dashboard.css.map*/
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,20 +0,0 @@
|
||||
(function($) {
|
||||
$(document).ready( () => {
|
||||
var count = $('.count_field');
|
||||
var typeRadio = $('input:radio[name="type"]');
|
||||
|
||||
var updateCountDispay = function(value) {
|
||||
if (value == 'dynamic') {
|
||||
count.slideUp(250);
|
||||
} else {
|
||||
count.slideDown(250);
|
||||
}
|
||||
};
|
||||
|
||||
updateCountDispay(typeRadio.filter(':checked').val());
|
||||
|
||||
typeRadio.change( event => {
|
||||
updateCountDispay(event.target.value);
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
@@ -3,124 +3,93 @@
|
||||
|
||||
{% block toggle_view %}to List {% endblock%}
|
||||
|
||||
{% block content_main %}
|
||||
<div>
|
||||
<div class="row">
|
||||
{% block content_cols %}
|
||||
{% block result_list %}
|
||||
<div class="block-container">
|
||||
{% if all_count %}
|
||||
{% for segment in object_list %}
|
||||
<div class="block block__segment block__segment--{{ segment.status }} segment--match-{{ segment.match_any|yesno:"any,all" }} segment--{{ segment.persistent|yesno:"persistent,fleeting" }}">
|
||||
|
||||
{% block filters %}
|
||||
{% if view.has_filters and all_count %}
|
||||
<div class="changelist-filter col3">
|
||||
<h2>{% trans 'Filter' %}</h2>
|
||||
{% for spec in view.filter_specs %}{% admin_list_filter view spec %}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<div>
|
||||
{% block result_list %}
|
||||
<div class="nice-padding block_container">
|
||||
{% if all_count %}
|
||||
{% for segment in object_list %}
|
||||
<div class="block block--{{ segment.status }}" onclick="location.href = 'edit/{{ segment.pk }}'">
|
||||
<h2>{{ segment }}</h2>
|
||||
<div class="inspect_container">
|
||||
<ul class="inspect segment_stats">
|
||||
<li class="stat_card">
|
||||
{% trans "This segment has been visited" %}
|
||||
<span class="icon icon-fa-rocket">{{ segment.visit_count|localize }} {% trans "time" %}{{ segment.visit_count|pluralize }}</span>
|
||||
</li>
|
||||
<li class="stat_card">
|
||||
{% trans "This segment has been active for" %}
|
||||
<span class="icon icon-fa-calendar">{{ segment.enable_date|days_since:segment.disable_date }} {% trans "day" %}{{ segment.enable_date|days_since:segment.disable_date|pluralize }}</span>
|
||||
</li>
|
||||
{% if segment.is_static %}
|
||||
<li class="stat_card">
|
||||
{% trans "This segment is Static" %}
|
||||
<span class="icon icon-fa-user">
|
||||
{{ segment.sessions.count|localize }}
|
||||
{% if segment.sessions.count < segment.count %}
|
||||
/ {{ segment.count }} {% trans "member" %}{{ segment.count|pluralize }}
|
||||
{% else %}
|
||||
{% trans "member" %}{{ segment.sessions.count|pluralize }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
|
||||
<ul class="inspect segment_rules">
|
||||
<li class="stat_card {{ segment.match_any|yesno:"any,all" }}">
|
||||
{% trans "The visitor must match" %}
|
||||
{% if segment.match_any %}
|
||||
<span class="icon icon-fa-cube">{% trans "Any rule" %}</span>
|
||||
{% else %}
|
||||
<span class="icon icon-fa-cubes">{% trans "All rules" %}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<li class="stat_card {{ segment.persistent|yesno:"persistent,fleeting" }}">
|
||||
{% trans "The persistence of this segment is" %}
|
||||
{% if segment.persistent %}
|
||||
<span class="icon icon-fa-bookmark" title="{% trans "This segment persists in between visits" %}">{% trans "Persistent" %}</span>
|
||||
{% else %}
|
||||
<span class="icon icon-fa-bookmark-o" title="{% trans "This segment is reevaluated on every visit" %}">{% trans "Fleeting" %}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% for rule in segment.get_rules %}
|
||||
<li class="stat_card {{ rule.encoded_name }}">
|
||||
{{ rule.description.title }}
|
||||
{% if rule.description.code %}
|
||||
<pre>{{ rule.description.value }}</pre>
|
||||
{% else %}
|
||||
<span class="icon icon-{{ rule.icon }}">{{ rule.description.value }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if user_can_create %}
|
||||
<ul class="block_actions">
|
||||
{% if segment.status == segment.STATUS_DISABLED %}
|
||||
<li><a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Enable this segment" %}">enable</a></li>
|
||||
{% elif segment.status == segment.STATUS_ENABLED %}
|
||||
<li><a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Disable this segment" %}">disable</a></li>
|
||||
{% endif %}
|
||||
<li><a href="edit/{{ segment.pk }}" title="{% trans "Configure this segment" %}">configure this</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if user_can_create %}
|
||||
{% blocktrans with url=view.create_url name=view.verbose_name %}
|
||||
<a class="block suggestion" href="{{ url }}">
|
||||
<span class="suggestive_text">Add a new {{name}}</span>
|
||||
</a>
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
<div class="block-header">
|
||||
<h2>{{ segment.name }}</h2>
|
||||
<div class="block-data">
|
||||
<p class="block-data__description">{% trans "This segment has been visited" %}</p>
|
||||
<p class="block-data__content">
|
||||
<strong>{{ segment.visit_count|localize }}</strong>
|
||||
{% trans "time" %}{{ segment.visit_count|pluralize }} {% trans "in" %}
|
||||
<strong>{{ segment.enable_date|days_since:segment.disable_date }}</strong>
|
||||
{% trans "day" %}{{ segment.enable_date|days_since:segment.disable_date|pluralize }}
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block pagination %}
|
||||
{% if paginator.num_pages > 1 %}
|
||||
<div class="pagination {% if view.has_filters and all_count %}col9{% else %}col12{% endif %}">
|
||||
<p>{% blocktrans with page_obj.number as current_page and paginator.num_pages as num_pages %}Page {{ current_page }} of {{ num_pages }}.{% endblocktrans %}</p>
|
||||
<ul>
|
||||
{% pagination_link_previous page_obj view %}
|
||||
{% pagination_link_next page_obj view %}
|
||||
<div class="block-rules">
|
||||
{% for rule in segment.get_rules %}
|
||||
<div class="block-data block-data--icon icon icon-{{ rule.icon }}">
|
||||
<p class="block-data__description">{{ rule.description.title }}</p>
|
||||
{% if rule.description.code %}
|
||||
<pre class="block-data__content block-data__content--code">{{ rule.description.value }}</pre>
|
||||
{% else %}
|
||||
<p class="block-data__content block-data__content--text">
|
||||
<strong>{{ rule.description.value }}</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="block-footer">
|
||||
<div class="block-stats">
|
||||
<p class="block-stats__content">
|
||||
{% trans "Active on" %}
|
||||
<strong>{{ segment.get_used_pages|length }}</strong>
|
||||
{% trans "page" %}{{ segment.get_used_pages|length|pluralize }} {% trans "and" %}
|
||||
<strong>{{ segment.get_created_variants|length }}</strong>
|
||||
{% trans "variant" %}{{ segment.get_created_variants|length|pluralize }}.
|
||||
</p>
|
||||
</div>
|
||||
<ul class="block-actions">
|
||||
{% if segment.status == segment.STATUS_DISABLED %}
|
||||
<li class="block-actions__action">
|
||||
{% if user_can_create %}
|
||||
<a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Enable this segment" %}">enable</a>
|
||||
{% else %}
|
||||
<span>enabled</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% elif segment.status == segment.STATUS_ENABLED %}
|
||||
<li class="block-actions__action">
|
||||
{% if user_can_create %}
|
||||
<a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Disable this segment" %}">disable</a>
|
||||
{% else %}
|
||||
<span>disabled</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user_can_create %}
|
||||
<li class="block-actions__action">
|
||||
<a href="{% url 'wagtail_personalisation_segment_modeladmin_edit' segment.pk %}" title="{% trans "Edit this segment" %}">edit</a>
|
||||
</li>
|
||||
<li class="block-actions__action">
|
||||
<a href="{% url 'wagtail_personalisation_segment_modeladmin_delete' segment.pk %}" title="{% trans "Delete this segment" %}">delete</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="block-actions__action">
|
||||
<a href="{% url 'wagtail_personalisation_segment_modeladmin_inspect' segment.pk %}" title="{% trans "Inspect this segment" %}">inspect</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if user_can_create %}
|
||||
{% blocktrans with url=view.create_url name=view.verbose_name %}
|
||||
<a class="block block-suggestion" href="{{ url }}">
|
||||
<span class="block-suggestion__text">Add a new {{ name }}</span>
|
||||
</a>
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@@ -46,6 +46,7 @@ class SegmentModelAdmin(ModelAdmin):
|
||||
index_view_extra_css = ['css/index.css']
|
||||
form_view_extra_js = ['js/commons.js', 'js/form.js']
|
||||
form_view_extra_css = ['css/form.css']
|
||||
inspect_view_enabled = True
|
||||
|
||||
def index_view(self, request):
|
||||
kwargs = {'model_admin': self}
|
||||
|
@@ -44,8 +44,3 @@ class RequestFactory(BaseRequestFactory):
|
||||
request.session = SessionStore()
|
||||
request._messages = FallbackStorage(request)
|
||||
return request
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user(django_user_model):
|
||||
return django_user_model.objects.create(username='user')
|
||||
|
@@ -1,248 +0,0 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from django.forms.models import model_to_dict
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
import pytest
|
||||
from wagtail_personalisation.forms import SegmentAdminForm
|
||||
from wagtail_personalisation.models import Segment
|
||||
from wagtail_personalisation.rules import TimeRule, VisitCountRule
|
||||
|
||||
from tests.factories.segment import SegmentFactory
|
||||
|
||||
|
||||
def form_with_data(segment, *rules):
|
||||
model_fields = ['type', 'status', 'count', 'name', 'match_any']
|
||||
|
||||
class TestSegmentAdminForm(SegmentAdminForm):
|
||||
class Meta:
|
||||
model = Segment
|
||||
fields = model_fields
|
||||
|
||||
data = model_to_dict(segment, model_fields)
|
||||
for formset in TestSegmentAdminForm().formsets.values():
|
||||
rule_data = {}
|
||||
count = 0
|
||||
for rule in rules:
|
||||
if isinstance(rule, formset.model):
|
||||
rule_data = model_to_dict(rule)
|
||||
for key, value in rule_data.items():
|
||||
data['{}-{}-{}'.format(formset.prefix, count, key)] = value
|
||||
count += 1
|
||||
data['{}-INITIAL_FORMS'.format(formset.prefix)] = 0
|
||||
data['{}-TOTAL_FORMS'.format(formset.prefix)] = count
|
||||
return TestSegmentAdminForm(data)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_session_added_to_static_segment_at_creation(site, client, user):
|
||||
session = client.session
|
||||
session.save()
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
assert user in instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_anonymous_user_not_added_to_static_segment_at_creation(site, client):
|
||||
session = client.session
|
||||
session.save()
|
||||
client.get(site.root_page.url)
|
||||
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
assert not instance.static_users.all()
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_match_any_correct_populates(site, client, django_user_model):
|
||||
user = django_user_model.objects.create(username='first')
|
||||
session = client.session
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
other_user = django_user_model.objects.create(username='second')
|
||||
client.cookies.clear()
|
||||
second_session = client.session
|
||||
other_page = site.root_page.get_last_child()
|
||||
client.force_login(other_user)
|
||||
client.get(other_page.url)
|
||||
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, match_any=True)
|
||||
rule_1 = VisitCountRule(counted_page=site.root_page)
|
||||
rule_2 = VisitCountRule(counted_page=other_page)
|
||||
form = form_with_data(segment, rule_1, rule_2)
|
||||
instance = form.save()
|
||||
|
||||
assert session.session_key != second_session.session_key
|
||||
assert user in instance.static_users.all()
|
||||
assert other_user in instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_mixed_static_dynamic_session_doesnt_generate_at_creation(site, client, user):
|
||||
session = client.session
|
||||
session.save()
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
||||
static_rule = VisitCountRule(counted_page=site.root_page)
|
||||
non_static_rule = TimeRule(
|
||||
start_time=datetime.time(0, 0, 0),
|
||||
end_time=datetime.time(23, 59, 59),
|
||||
)
|
||||
form = form_with_data(segment, static_rule, non_static_rule)
|
||||
instance = form.save()
|
||||
|
||||
assert not instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_session_not_added_to_static_segment_after_creation(site, client, user):
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=0)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
session = client.session
|
||||
session.save()
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
assert not instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_session_added_to_static_segment_after_creation(site, client, user):
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
session = client.session
|
||||
session.save()
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
assert user in instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_anonymou_user_not_added_to_static_segment_after_creation(site, client):
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
session = client.session
|
||||
session.save()
|
||||
client.get(site.root_page.url)
|
||||
|
||||
assert not instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_session_not_added_to_static_segment_after_full(site, client, django_user_model):
|
||||
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)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
assert not instance.static_users.all()
|
||||
|
||||
session = client.session
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
assert instance.static_users.count() == 1
|
||||
|
||||
client.cookies.clear()
|
||||
second_session = client.session
|
||||
client.force_login(other_user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
assert session.session_key != second_session.session_key
|
||||
assert instance.static_users.count() == 1
|
||||
assert user in instance.static_users.all()
|
||||
assert other_user not in instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sessions_not_added_to_static_segment_if_rule_not_static(client, site, user):
|
||||
session = client.session
|
||||
session.save()
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1)
|
||||
rule = TimeRule(
|
||||
start_time=datetime.time(0, 0, 0),
|
||||
end_time=datetime.time(23, 59, 59),
|
||||
segment=segment,
|
||||
)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
assert not instance.static_users.all()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_does_not_calculate_the_segment_again(site, client, mocker, user):
|
||||
session = client.session
|
||||
session.save()
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=2)
|
||||
rule = VisitCountRule(counted_page=site.root_page, segment=segment)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
|
||||
assert user in instance.static_users.all()
|
||||
|
||||
mock_test_rule = mocker.patch('wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
||||
client.get(site.root_page.url)
|
||||
assert mock_test_rule.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_non_static_rules_have_a_count():
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=0)
|
||||
rule = TimeRule(
|
||||
start_time=datetime.time(0, 0, 0),
|
||||
end_time=datetime.time(23, 59, 59),
|
||||
segment=segment,
|
||||
)
|
||||
form = form_with_data(segment, rule)
|
||||
assert not form.is_valid()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_static_segment_with_static_rules_needs_no_count(site):
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=0)
|
||||
rule = VisitCountRule(counted_page=site.root_page, segment=segment)
|
||||
form = form_with_data(segment, rule)
|
||||
assert form.is_valid()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_dynamic_segment_with_non_static_rules_have_a_count():
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC, count=0)
|
||||
rule = TimeRule(
|
||||
start_time=datetime.time(0, 0, 0),
|
||||
end_time=datetime.time(23, 59, 59),
|
||||
)
|
||||
form = form_with_data(segment, rule)
|
||||
assert form.is_valid(), form.errors
|
8
tox.ini
8
tox.ini
@@ -1,16 +1,18 @@
|
||||
[tox]
|
||||
envlist = py{27}-django{19}-wagtail{19},lint
|
||||
envlist = py{27,35,36}-django{19,110,111}-wagtail{19,110},lint
|
||||
|
||||
[testenv]
|
||||
commands = coverage run --parallel -m pytest {posargs}
|
||||
extras = test
|
||||
deps =
|
||||
django19: django>=1.9,<1.10
|
||||
django110: django>=1.10<1.11
|
||||
django111: django>=1.11,<1.12
|
||||
wagtail19: wagtail>=1.9,<1.10
|
||||
wagtail110: wagtail>=1.10,<1.11
|
||||
|
||||
[testenv:coverage-report]
|
||||
basepython = python2.7
|
||||
basepython = python3.5
|
||||
deps = coverage
|
||||
pip_pre = true
|
||||
skip_install = true
|
||||
@@ -20,7 +22,7 @@ commands =
|
||||
|
||||
|
||||
[testenv:lint]
|
||||
basepython = python2.7
|
||||
basepython = python3.5
|
||||
deps = flake8
|
||||
commands =
|
||||
flake8 src tests setup.py
|
||||
|
Reference in New Issue
Block a user