summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2023-04-06 18:36:47 +0200
committerÉtienne Loks <etienne.loks@iggdrasil.net>2023-04-17 15:47:16 +0200
commit71a256dc52ed3391638dcf9669cf57d75475d326 (patch)
tree422181b331fcd809a6c4a4316d76d960a248f4d5 /ishtar_common
parent59d92f268b2a002b006250258bdc54880e080013 (diff)
downloadIshtar-71a256dc52ed3391638dcf9669cf57d75475d326.tar.bz2
Ishtar-71a256dc52ed3391638dcf9669cf57d75475d326.zip
Display of a changelog with alert display when updates are made
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/context_processors.py70
-rw-r--r--ishtar_common/migrations/0227_auto_20230406_1834.py (renamed from ishtar_common/migrations/0227_auto_20230404_1112.py)12
-rw-r--r--ishtar_common/models.py6
-rw-r--r--ishtar_common/templates/ishtar/changelog.html18
-rw-r--r--ishtar_common/templates/navbar.html3
-rw-r--r--ishtar_common/tests.py48
-rw-r--r--ishtar_common/urls.py3
-rw-r--r--ishtar_common/views.py50
8 files changed, 199 insertions, 11 deletions
diff --git a/ishtar_common/context_processors.py b/ishtar_common/context_processors.py
index c1d1224ea..8131e0382 100644
--- a/ishtar_common/context_processors.py
+++ b/ishtar_common/context_processors.py
@@ -18,6 +18,8 @@
# See the file COPYING for details.
import datetime
+import os
+import re
from django.conf import settings
from django.core.cache import cache
@@ -33,6 +35,45 @@ from bootstrap_datepicker.widgets import DatePicker
from .menus import Menu
+def _get_changelog_version_from_file():
+ changelog_dir = os.path.join(
+ settings.SHARE_BASE_PATH,
+ "changelog",
+ settings.LANGUAGE_CODE.split('-')[0]
+ )
+ if not os.path.exists(changelog_dir):
+ return "no-version"
+ changelog_file = os.path.join(changelog_dir, f"changelog_1.md")
+ if not os.path.exists(changelog_file):
+ return "no-version"
+ current_version = None
+ with open(changelog_file, "r") as changelog:
+ for line in changelog.readlines():
+ m = re.match(r"v(\d+)\.(\d+)\.(\d+)", line)
+ if not m:
+ continue
+ g = m.groups()
+ if len(g) != 3:
+ continue
+ current_version = ".".join(g)
+ break
+ if not current_version:
+ return "no-version"
+ return current_version
+
+
+def get_changelog_version():
+ cache_key = f"{settings.PROJECT_SLUG}-news-version"
+ current_version = cache.get(cache_key)
+ if current_version:
+ return current_version
+
+ if not current_version:
+ current_version = _get_changelog_version_from_file()
+ cache.set(cache_key, current_version, settings.CACHE_LONGTIMEOUT)
+ return current_version
+
+
def get_base_context(request):
dct = {
"URL_PATH": settings.URL_PATH,
@@ -83,15 +124,36 @@ def get_base_context(request):
cache.set(key, password_expired, settings.CACHE_TIMEOUT)
if password_expired and not request.path.endswith("password_change/"):
msg = str(_("Your password has expired. Please update it using this "
- "form."))
+ "<form>."))
msg = msg.replace(
- str(_("form")),
+ "<form>",
f'<a href="{reverse("password_change")}">'
- f'<i class="fa fa-external-link" aria-hidden="true"></i> '
- f'{_("form")}</a>'
+ f'{_("form")} '
+ f'<i class="fa fa-external-link" aria-hidden="true"></i>'
+ '</a>'
)
dct["MESSAGES"].append((msg, "warning"))
+ # check changelog
+ if request.user.ishtaruser.display_news:
+ user_version = request.user.ishtaruser.latest_news_version
+ current_version = get_changelog_version()
+ if current_version != user_version and "changelog" not in dct["CURRENT_PATH"]:
+ if user_version:
+ msg = str(_("Ishtar have been updated from version <old-version> to <new-version>. "
+ "Check the <changelog>."))
+ else:
+ msg = str(_("Ishtar have been updated to version <new-version>. Check the "
+ "<changelog>."))
+ msg = msg.replace(
+ "<changelog>",
+ f'<a href="{reverse("changelog")}">'
+ f'{_("changelog")} '
+ f'<i class="fa fa-external-link" aria-hidden="true"></i>'
+ f'</a>'
+ ).replace("<old-version>", user_version).replace("<new-version>", current_version)
+ dct["MESSAGES"].append((msg, "info"))
+
# external sources
if (
request.user.ishtaruser.current_profile
diff --git a/ishtar_common/migrations/0227_auto_20230404_1112.py b/ishtar_common/migrations/0227_auto_20230406_1834.py
index 1ba74f292..a365c0bc8 100644
--- a/ishtar_common/migrations/0227_auto_20230404_1112.py
+++ b/ishtar_common/migrations/0227_auto_20230406_1834.py
@@ -1,4 +1,4 @@
-# Generated by Django 2.2.24 on 2023-04-04 11:12
+# Generated by Django 2.2.24 on 2023-04-06 18:34
import datetime
from django.db import migrations, models
@@ -13,6 +13,16 @@ class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='ishtaruser',
+ name='display_news',
+ field=models.BooleanField(default=True, verbose_name='Display news'),
+ ),
+ migrations.AddField(
+ model_name='ishtaruser',
+ name='latest_news_version',
+ field=models.CharField(blank=True, default='', max_length=20, verbose_name='Latest news version'),
+ ),
+ migrations.AddField(
+ model_name='ishtaruser',
name='password_last_update',
field=models.DateField(default=datetime.date.today, verbose_name='Password last update'),
),
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index efa061431..f7baebfe4 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -3416,6 +3416,10 @@ class IshtarUser(FullSearch):
advanced_shortcut_menu = models.BooleanField(
_("Advanced shortcut menu"), default=False
)
+ # latest news read by the user
+ latest_news_version = models.CharField(_("Latest news version"), default="", blank=True,
+ max_length=20)
+ display_news = models.BooleanField(_("Display news"), default=True)
class Meta:
verbose_name = _("Ishtar user")
@@ -3500,7 +3504,7 @@ class IshtarUser(FullSearch):
@post_importer_action
def import_create_profile(self, context, value):
UserProfile.objects.get_or_create(person=self.person, profile_type=value,
- defaults={"name":value.label})
+ defaults={"name": value.label})
post_save.connect(cached_label_changed, sender=IshtarUser)
diff --git a/ishtar_common/templates/ishtar/changelog.html b/ishtar_common/templates/ishtar/changelog.html
new file mode 100644
index 000000000..e8fb94ea8
--- /dev/null
+++ b/ishtar_common/templates/ishtar/changelog.html
@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block content %}
+<div class="container">
+ <h1>{% trans "Changelog" %}</h1>
+ {% if next %}
+ <div class="text-center">
+ <a class="btn btn-info" href="{% url 'changelog' next %}">{% trans "Next" %}</a>
+ </div>
+ {% endif %}
+ {{changelog|safe}}
+ {% if previous %}
+ <div class="text-center">
+ <a class="btn btn-info" href="{% url 'changelog' previous %}">{% trans "Previous" %}</a>
+ </div>
+ {% endif %}
+</div>
+{% endblock %}
diff --git a/ishtar_common/templates/navbar.html b/ishtar_common/templates/navbar.html
index d8e368bbc..3e7190645 100644
--- a/ishtar_common/templates/navbar.html
+++ b/ishtar_common/templates/navbar.html
@@ -33,6 +33,9 @@
<a class="dropdown-item" href="{% url 'password_change' %}">
{% trans "Change password" %}
</a>
+ <a class="dropdown-item" href="{% url 'changelog' %}">
+ {% trans "Changelog" %}
+ </a>
<a class="dropdown-item" href="{% url 'logout' %}">
{% trans "Log out" %}
</a>
diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py
index 774fab2a2..97002f13d 100644
--- a/ishtar_common/tests.py
+++ b/ishtar_common/tests.py
@@ -1348,9 +1348,9 @@ class UserProfileTest(TestCase):
self.user.set_password(self.password)
self.user.save()
self.client = Client()
- self.client.login(username=self.username, password=self.password)
def test_profile_edit(self):
+ self.client.login(username=self.username, password=self.password)
base_url = "/profile/"
base_profile = self.user.ishtaruser.current_profile
response = self.client.get(base_url)
@@ -1407,6 +1407,52 @@ class UserProfileTest(TestCase):
self.assertEqual(self.user.ishtaruser.person.profiles.count(), 1)
+ def _check_changelog_alert(self, content, check_ok=True):
+ soup = Soup(content, "lxml")
+ messages = soup.findAll("div", {"id": "message_list"})
+ if not check_ok and not len(messages):
+ return
+ self.assertEqual(len(messages), 1)
+ for div in messages:
+ content = str(div.extract())
+ if check_ok:
+ self.assertIn("/changelog/", content)
+ else:
+ self.assertNotIn("/changelog/", content)
+
+ def test_changelog(self):
+ url = "/changelog/"
+ response = self.client.get(url)
+ self.assertRedirects(response, "/accounts/login/?next={}".format(url))
+ self.client.login(username=self.username, password=self.password)
+ user = models.IshtarUser.objects.get(pk=self.user.pk)
+ self.assertEqual(user.latest_news_version, "")
+ user.display_news = False
+ user.save()
+
+ response = self.client.get("/")
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get("/")
+ # 2 times because the first page on server start do not have the version in cache
+ # not a big deal at all...
+
+ # display_news set to False
+ self._check_changelog_alert(response.content.decode(), check_ok=False)
+ user.display_news = True
+ user.save()
+ response = self.client.get("/")
+ self._check_changelog_alert(response.content.decode())
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ user = models.IshtarUser.objects.get(pk=self.user.pk)
+ # checked version updated
+ self.assertNotEqual(user.latest_news_version, "")
+ # now no link to changelog displayed
+ response = self.client.get("/")
+ self._check_changelog_alert(response.content.decode(), check_ok=False)
+
+
class AcItem:
def __init__(
self,
diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py
index c5b3816a4..ad7e22019 100644
--- a/ishtar_common/urls.py
+++ b/ishtar_common/urls.py
@@ -422,8 +422,7 @@ urlpatterns += [
views.autocomplete_organization,
name="autocomplete-organization",
),
- # TODO v4: suppression
- #url(r"admin-globalvar/", views.GlobalVarEdit.as_view(), name="admin-globalvar"),
+ url(r"changelog/(?:(?P<page>\d+)/)?", views.ChangelogView.as_view(), name="changelog"),
url(r"person-merge/(?:(?P<page>\d+)/)?$", views.person_merge, name="person_merge"),
url(
r"person-manual-merge/$",
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index f5a58afad..ac4e995d1 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -24,6 +24,7 @@ from jinja2 import TemplateSyntaxError
import json
import logging
import os
+import re
import unicodedata
import urllib.parse
@@ -1115,11 +1116,11 @@ organization_merge = merge_action(
)
-class IshtarMixin(object):
+class IshtarMixin:
page_name = ""
def get_context_data(self, **kwargs):
- context = super(IshtarMixin, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
@@ -1149,6 +1150,51 @@ class AdminLoginRequiredMixin(LoginRequiredMixin):
return super(AdminLoginRequiredMixin, self).dispatch(request, *args, **kwargs)
+class ChangelogView(IshtarMixin, LoginRequiredMixin, TemplateView):
+ template_name = "ishtar/changelog.html"
+ page_name = _("Changelog")
+ current_url = "changelog"
+
+ def update_read_version(self):
+ if not self.request.user or not hasattr(self.request.user, "ishtaruser") \
+ or not self.request.user.ishtaruser:
+ return
+ cache_key = f"{settings.PROJECT_SLUG}-news-version"
+ current_version = cache.get(cache_key)
+ if not current_version:
+ return
+ ishtar_user = models.IshtarUser.objects.get(pk=self.request.user.ishtaruser.pk)
+ if not ishtar_user.latest_news_version \
+ or ishtar_user.latest_news_version != current_version:
+ ishtar_user.latest_news_version = current_version
+ ishtar_user.save()
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ changelog_dir = os.path.join(
+ settings.SHARE_BASE_PATH,
+ "changelog",
+ settings.LANGUAGE_CODE.split('-')[0]
+ )
+ if not os.path.exists(changelog_dir):
+ raise Http404()
+ page_number = int(kwargs.get("page", 0) or 0) or 1
+ if page_number == 1:
+ self.update_read_version()
+ changelog_file = os.path.join(changelog_dir, f"changelog_{page_number}.md")
+ if not os.path.exists(changelog_file):
+ raise Http404()
+ with open(changelog_file, "r") as changelog:
+ context["changelog"] = markdown(changelog.read())
+ if page_number > 1:
+ context["next"] = page_number - 1
+
+ changelog_file_next = os.path.join(changelog_dir, f"changelog_{page_number + 1}.md")
+ if os.path.exists(changelog_file_next):
+ context["previous"] = page_number + 1
+ return context
+
+
class ProfileEdit(LoginRequiredMixin, FormView):
template_name = "ishtar/forms/profile.html"
form_class = forms.ProfilePersonForm