diff options
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/context_processors.py | 70 | ||||
-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.py | 6 | ||||
-rw-r--r-- | ishtar_common/templates/ishtar/changelog.html | 18 | ||||
-rw-r--r-- | ishtar_common/templates/navbar.html | 3 | ||||
-rw-r--r-- | ishtar_common/tests.py | 48 | ||||
-rw-r--r-- | ishtar_common/urls.py | 3 | ||||
-rw-r--r-- | ishtar_common/views.py | 50 |
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 |