kb: add cell to display last page updates (#39091)
This commit is contained in:
parent
bd558163a8
commit
e451097bef
|
@ -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'
|
|
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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
|
|
@ -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 %}
|
|
@ -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',
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue