summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas ROCHE <nroche@entrouvert.com>2020-01-31 15:45:27 (GMT)
committerNicolas ROCHE <nroche@entrouvert.com>2020-01-31 15:45:54 (GMT)
commit98543f53aac2722f4d167956da81990d01b14805 (patch)
tree880892be559092b9d2faffcc93ca62845e817a65
parent49eb65f3f41ae82d9563561dc7d6fc0ea62eaae9 (diff)
downloadcombo-wip/39091-last-updated-cell.zip
combo-wip/39091-last-updated-cell.tar.gz
combo-wip/39091-last-updated-cell.tar.bz2
kb: add cell to display last page updates (#39091)wip/39091-last-updated-cell
-rw-r--r--combo/apps/kb/__init__.py26
-rw-r--r--combo/apps/kb/migrations/0001_initial.py39
-rw-r--r--combo/apps/kb/migrations/__init__.py0
-rw-r--r--combo/apps/kb/models.py46
-rw-r--r--combo/apps/kb/templates/combo/latest-page-updates-cell.html22
-rw-r--r--combo/settings.py1
-rw-r--r--tests/test_kb.py137
7 files changed, 271 insertions, 0 deletions
diff --git a/combo/apps/kb/__init__.py b/combo/apps/kb/__init__.py
new file mode 100644
index 0000000..6336152
--- /dev/null
+++ b/combo/apps/kb/__init__.py
@@ -0,0 +1,26 @@
+# combo - content management system
+# Copyright (C) 2020 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import django.apps
+from django.utils.translation import ugettext_lazy as _
+
+
+class AppConfig(django.apps.AppConfig):
+ name = 'combo.apps.kb'
+ verbose_name = _('Knowledge Base')
+
+
+default_app_config = 'combo.apps.kb.AppConfig'
diff --git a/combo/apps/kb/migrations/0001_initial.py b/combo/apps/kb/migrations/0001_initial.py
new file mode 100644
index 0000000..f84ff4b
--- /dev/null
+++ b/combo/apps/kb/migrations/0001_initial.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2020-01-31 15:40
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('data', '0042_page_creation_timestamp'),
+ ('auth', '0008_alter_user_username_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='LatestPageUpdatesCell',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('placeholder', models.CharField(max_length=20)),
+ ('order', models.PositiveIntegerField()),
+ ('slug', models.SlugField(blank=True, verbose_name='Slug')),
+ ('extra_css_class', models.CharField(blank=True, max_length=100, verbose_name='Extra classes for CSS styling')),
+ ('public', models.BooleanField(default=True, verbose_name='Public')),
+ ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
+ ('last_update_timestamp', models.DateTimeField(auto_now=True)),
+ ('limit', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Maximum number of entries')),
+ ('groups', models.ManyToManyField(blank=True, to='auth.Group', verbose_name='Groups')),
+ ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='data.Page')),
+ ('root_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='kb_latest_page_updates_cell_root_page', to='data.Page', verbose_name='Root Page')),
+ ],
+ options={
+ 'verbose_name': 'Latest Page Updates',
+ },
+ ),
+ ]
diff --git a/combo/apps/kb/migrations/__init__.py b/combo/apps/kb/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/combo/apps/kb/migrations/__init__.py
diff --git a/combo/apps/kb/models.py b/combo/apps/kb/models.py
new file mode 100644
index 0000000..2714e06
--- /dev/null
+++ b/combo/apps/kb/models.py
@@ -0,0 +1,46 @@
+# combo - content management system
+# Copyright (C) 2020 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+
+from django.db import models
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+
+from combo.data.models import CellBase, Page
+from combo.data.library import register_cell_class
+
+@register_cell_class
+class LatestPageUpdatesCell(CellBase):
+ root_page = models.ForeignKey(
+ Page, on_delete=models.SET_NULL, null=True, blank=True,
+ verbose_name=_('Root Page'), related_name='kb_latest_page_updates_cell_root_page')
+ limit = models.PositiveSmallIntegerField(
+ _('Maximum number of entries'), null=True, blank=True)
+
+ template_name = 'combo/latest-page-updates-cell.html'
+
+ class Meta:
+ verbose_name = _('Latest Page Updates')
+
+ def get_cell_extra_context(self, context):
+ extra_context = super(LatestPageUpdatesCell, self).get_cell_extra_context(context)
+ if self.root_page:
+ pages = self.root_page.get_descendants_and_me()
+ else:
+ pages = Page.objects.all()
+ extra_context['pages'] = pages.order_by('-last_update_timestamp')[:self.limit]
+ return extra_context
diff --git a/combo/apps/kb/templates/combo/latest-page-updates-cell.html b/combo/apps/kb/templates/combo/latest-page-updates-cell.html
new file mode 100644
index 0000000..ae71080
--- /dev/null
+++ b/combo/apps/kb/templates/combo/latest-page-updates-cell.html
@@ -0,0 +1,22 @@
+{% load i18n %}
+{% block cell-content %}
+<h2>{% trans "Latest Page Updates" %}</h2>
+<div class="links-list">
+ <ul class="latest-page-updates-cell--list">
+ {% for page in pages %}
+ <li class="latest-page-updates-cell--item">
+ <a href="{{ page.url }}">
+ <span class="latest-page-updates-cell--item-title">{{ page.title }}</span>
+ <time
+ class="latest-page-updates-cell--item-date"
+ datetime="{{ page.last_update_timestamp|datetime|date:"c" }}"
+ >{% trans "on" %} {{ page.last_update_timestamp }}</time>
+ {% if page.is_new %}
+ <span class="latest-page-updates-cell--item-isnew">{% trans "(new page)" %}</span>
+ {% endif %}
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+</div>
+{% endblock %}
diff --git a/combo/settings.py b/combo/settings.py
index 7f63e3f..8d71475 100644
--- a/combo/settings.py
+++ b/combo/settings.py
@@ -76,6 +76,7 @@ INSTALLED_APPS = (
'combo.apps.calendar',
'combo.apps.pwa',
'combo.apps.gallery',
+ 'combo.apps.kb',
'haystack',
'xstatic.pkg.josefinsans',
'xstatic.pkg.leaflet',
diff --git a/tests/test_kb.py b/tests/test_kb.py
new file mode 100644
index 0000000..4e126d7
--- /dev/null
+++ b/tests/test_kb.py
@@ -0,0 +1,137 @@
+# combo - content management system
+# Copyright (C) 2020 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.import pytest
+
+import pytest
+import re
+
+from combo.data.models import CellBase, Page, TextCell
+from combo.apps.kb.models import LatestPageUpdatesCell
+
+pytestmark = pytest.mark.django_db
+
+
+def login(app, username='admin', password='admin'):
+ login_page = app.get('/login/')
+ login_form = login_page.forms[0]
+ login_form['username'] = username
+ login_form['password'] = password
+ resp = login_form.submit()
+ assert resp.status_int == 302
+ return app
+
+
+@pytest.mark.freeze_time('2020-01-01')
+def test_manage_updated_pages_cell(app, admin_user):
+ page = Page(title='example page', slug='example-page')
+ page.save()
+ app = login(app)
+ resp = app.get('/manage/pages/%s/' % page.id, status=200)
+
+ optgroup = resp.html.find('optgroup', attrs={'label': 'Knowledge Base'})
+ add_cell_url = optgroup.findChild().attrs['data-add-url']
+ assert 'kb_latestpageupdatescell' in add_cell_url
+ resp = app.get(add_cell_url, status=302)
+ resp = resp.follow()
+ cells = CellBase.get_cells(page_id=page.id)
+ assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text
+ resp = resp.forms[0].submit()
+ assert resp.status_int == 302
+
+ resp = app.get('/example-page/', status=200)
+ div = resp.html.find('div', attrs={'class': 'latest-page-updates-cell'})
+ assert 'on Jan. 1, 2020' in div.findChild('time').text
+
+def test_updated_pages_cell_new_page(freezer):
+ freezer.move_to('2020-01-01')
+ page = Page(title='example page', slug='example-page')
+ page.save()
+ cell = LatestPageUpdatesCell(page=page, order=0)
+ cell.save()
+ ctx = {}
+ assert 'on Jan. 1, 2020' in cell.render(ctx)
+ assert page.snapshot is None
+ assert '(new page)' in cell.render(ctx)
+
+ freezer.move_to('2020-01-08')
+ assert '(new page)' not in cell.render(ctx)
+
+def test_updated_pages_cell_limit(freezer):
+ for number in range(1, 4):
+ page = Page(title='page %s' % number, slug='page-%i' % number)
+ page.save()
+ cell = LatestPageUpdatesCell(page=page, order=0)
+ cell.save()
+ ctx = {}
+ assert cell.render(ctx).count('<li') == 3
+
+ cell.limit = 2
+ cell.save()
+ assert cell.render(ctx).count('<li') == 2
+
+def test_updated_pages_cell_sort(freezer):
+ for day in [30, 11, 2, 22]:
+ freezer.move_to('2020-01-%02i' % day)
+ page = Page(title='page %s' % day, slug='page-%i' % day)
+ page.save()
+ cell = TextCell(page=page, order=0, slug='cell-%i' % day, text='foo')
+ cell.save()
+ cell = LatestPageUpdatesCell(page=page, order=0, limit=3)
+ cell.save()
+ ctx = {}
+ assert len(re.findall('<time', cell.render(ctx))) == 3
+ times = re.finditer('<time[^>]*>([^<]*)</time>', cell.render(ctx))
+ assert 'on Jan. 30, 2020' in next(times).group(0)
+ assert 'on Jan. 22, 2020' in next(times).group(0)
+ assert 'on Jan. 11, 2020' in next(times).group(0)
+
+ # update contained cell
+ freezer.move_to('2020-01-31')
+ text_cell = TextCell.objects.get(slug='cell-2')
+ text_cell.text = 'bar'
+ text_cell.save()
+ assert len(re.findall('<time', cell.render(ctx))) == 3
+ times = re.finditer('<time[^>]*>([^<]*)</time>', cell.render(ctx))
+ assert 'on Jan. 31, 2020' in next(times).group(0)
+ assert 'on Jan. 30, 2020' in next(times).group(0)
+ assert 'on Jan. 22, 2020' in next(times).group(0)
+
+def test_updated_pages_cell_root_page():
+ ''' 1 3
+ |
+ 2
+ '''
+ page1 = Page(title='page-1', slug='page-1')
+ page1.save()
+ page2 = Page(title='page-2', slug='page-2')
+ page2.parent_id = page1.id
+ page2.save()
+ page3 = Page(title='page-3', slug='page-3')
+ page3.save()
+ cell = LatestPageUpdatesCell(slug='me')
+ cell.page = page3
+ cell.order = 0
+ cell.save()
+ ctx = {}
+ assert cell.render(ctx).count('<li') == 3
+
+ cell.root_page = Page.objects.get(slug='page-1')
+ assert cell.render(ctx).count('<li') == 2
+ cell.root_page = Page.objects.get(slug='page-2')
+ assert cell.render(ctx).count('<li') == 1
+
+ Page.objects.get(slug='page-2').delete()
+ cell = CellBase.get_cells(slug='me')[0] # reload cell
+ assert cell.render(ctx).count('<li') == 2