From 6593dedcbc0228c1d1907d186cec2bf0f2123afd Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 21 Oct 2024 18:09:16 +0200 Subject: [PATCH 1/7] :sparkles: 990 Implement new header --- src/sdg/conf/base.py | 1 + src/sdg/organisaties/views/notificaties.py | 18 +++- .../migrations/0059_notificationviewed.py | 38 +++++++ src/sdg/producten/models/__init__.py | 1 + src/sdg/producten/models/notification.py | 15 +++ src/sdg/scss/components/_header.scss | 80 ++------------- src/sdg/scss/components/_index.scss | 2 + src/sdg/scss/components/_nav.scss | 31 ++++++ src/sdg/scss/components/_user-dropdown.scss | 86 ++++++++++++++++ .../templates/core/_municipality_switch.html | 7 -- src/sdg/templates/core/base_home.html | 69 +------------ src/sdg/templates/core/index.html | 6 -- src/sdg/templates/navigation/nav_item.html | 11 +++ src/sdg/templates/navigation/navigation.html | 43 ++++++++ .../templates/navigation/user_dropdown.html | 23 +++++ src/sdg/utils/context_processors.py | 32 ++++++ src/sdg/utils/templatetags/utils.py | 98 ++++++++++++++++++- 17 files changed, 405 insertions(+), 156 deletions(-) create mode 100644 src/sdg/producten/migrations/0059_notificationviewed.py create mode 100644 src/sdg/producten/models/notification.py create mode 100644 src/sdg/scss/components/_nav.scss create mode 100644 src/sdg/scss/components/_user-dropdown.scss delete mode 100644 src/sdg/templates/core/_municipality_switch.html create mode 100644 src/sdg/templates/navigation/nav_item.html create mode 100644 src/sdg/templates/navigation/navigation.html create mode 100644 src/sdg/templates/navigation/user_dropdown.html diff --git a/src/sdg/conf/base.py b/src/sdg/conf/base.py index 273c856ca..ec8240f97 100644 --- a/src/sdg/conf/base.py +++ b/src/sdg/conf/base.py @@ -183,6 +183,7 @@ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "sdg.utils.context_processors.settings", + "sdg.utils.context_processors.has_new_notifications", ], "loaders": TEMPLATE_LOADERS, }, diff --git a/src/sdg/organisaties/views/notificaties.py b/src/sdg/organisaties/views/notificaties.py index 208dd12a9..f012d3751 100644 --- a/src/sdg/organisaties/views/notificaties.py +++ b/src/sdg/organisaties/views/notificaties.py @@ -1,4 +1,5 @@ from django.contrib.auth.mixins import LoginRequiredMixin +from django.utils import timezone from django.utils.timezone import now from django.utils.translation import gettext as _ from django.views.generic import ListView @@ -6,7 +7,7 @@ from dateutil.relativedelta import relativedelta from sdg.core.views.mixins import BreadcrumbsMixin -from sdg.producten.models import ProductVersie +from sdg.producten.models import NotificationViewed, ProductVersie class ProductVersieListView( @@ -41,3 +42,18 @@ def get_queryset(self): .published() .order_by("-gewijzigd_op")[: self.limit] ) + + def get(self, request, *args, **kwargs): + # Call the parent class's get method to fetch the queryset + response = super().get(request, *args, **kwargs) + + # Get or create the NotificationViewed instance for the current user + notification_viewed, create = NotificationViewed.objects.get_or_create( + gebruiker=request.user + ) + + # Update the last_viewed_date to the current time + notification_viewed.last_viewed_date = timezone.now() + notification_viewed.save() + + return response diff --git a/src/sdg/producten/migrations/0059_notificationviewed.py b/src/sdg/producten/migrations/0059_notificationviewed.py new file mode 100644 index 000000000..6e1e12807 --- /dev/null +++ b/src/sdg/producten/migrations/0059_notificationviewed.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.23 on 2024-10-21 14:12 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("producten", "0058_alter_localizedproduct_decentrale_procedure_link"), + ] + + operations = [ + migrations.CreateModel( + name="NotificationViewed", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("last_viewed_date", models.DateTimeField(blank=True, null=True)), + ( + "gebruiker", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/src/sdg/producten/models/__init__.py b/src/sdg/producten/models/__init__.py index 43ffa6f41..9e2a1778b 100644 --- a/src/sdg/producten/models/__init__.py +++ b/src/sdg/producten/models/__init__.py @@ -1,2 +1,3 @@ from .localized import * # noqa +from .notification import * # noqa from .product import * # noqa diff --git a/src/sdg/producten/models/notification.py b/src/sdg/producten/models/notification.py new file mode 100644 index 000000000..701cc4ecf --- /dev/null +++ b/src/sdg/producten/models/notification.py @@ -0,0 +1,15 @@ +from django.contrib.auth import get_user_model +from django.db import models +from django.utils import timezone + +User = get_user_model() + + +class NotificationViewed(models.Model): + gebruiker = models.OneToOneField(User, on_delete=models.CASCADE) + last_viewed_date = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return ( + f"{self.gebruiker} - Notification last viewed on: {self.last_viewed_date}" + ) diff --git a/src/sdg/scss/components/_header.scss b/src/sdg/scss/components/_header.scss index a1259c019..0cafe1cc1 100644 --- a/src/sdg/scss/components/_header.scss +++ b/src/sdg/scss/components/_header.scss @@ -1,48 +1,24 @@ -@import 'colors'; +@import "colors"; .header { - --shadow-color: rgba(0, 0, 0, 0.121722); - width: 100%; background: rgb(57, 106, 136); - background: linear-gradient(353.15deg, var(--org-theme-bg-darkest) -68.3%, var(--org-theme-bg) 179.94%); + background: linear-gradient( + 353.15deg, + var(--org-theme-bg-darkest) -68.3%, + var(--org-theme-bg) 179.94% + ); padding: 24px 0 0; justify-content: space-between; display: flex; flex-direction: column; - &.header--inset { - padding-bottom: 88px; - } - - .header__top { + &__top { justify-content: space-between; display: flex; flex-direction: row; } - .header__user { - display: flex; - align-items: center; - - & > .svg-inline--fa { - height: 50px; - width: 50px; - border-radius: 50%; - background-color: $color_primary_lightest; - color: $color_primary; - padding-top: 4px; - overflow: hidden; - margin-right: 8px; - border: 3px solid $color_secondary_dark; - z-index: 1; - - path { - color: $color_primary; - } - } - } - .header__username { background-color: var(--org-theme-bg-darker); border: 1px solid transparent; @@ -59,36 +35,10 @@ color: #000; } - .header__title { - color: $color_secondary_dark; - font-size: 40px; - - .switch-icon { - color: $color_accent; - margin-left: 8px; - } - - a:hover svg { - color: $color_accent_dark; - transition: all 0.3s ease-in-out; - } - - } - - .header__subtitle { - color: $color_secondary_darker; - font-size: 14px; - margin-bottom: 8px; - } - .header__dropdown { position: relative; padding: 12px; color: #fff; - - .svg-inline--fa:hover { - transform: scale(1.05); - } } .header__dropdown *:hover ~ .header__dropdown-list, @@ -126,20 +76,4 @@ text-decoration: underline; } } - - .header__dropdown-action .svg-inline--fa { - float: left; - margin-top: 2px; - margin-right: 8px; - width: 1em; - } - - .header__dropdown-text { - padding: 12px; - color: #000; - display: block; - text-align: left; - text-decoration: none; - margin-right: 8px; - } } diff --git a/src/sdg/scss/components/_index.scss b/src/sdg/scss/components/_index.scss index d86e35bd3..e41a6574a 100644 --- a/src/sdg/scss/components/_index.scss +++ b/src/sdg/scss/components/_index.scss @@ -34,3 +34,5 @@ @import 'url_label_field'; @import 'choices'; @import 'publications'; +@import 'nav'; +@import 'user-dropdown'; diff --git a/src/sdg/scss/components/_nav.scss b/src/sdg/scss/components/_nav.scss new file mode 100644 index 000000000..bbac36c81 --- /dev/null +++ b/src/sdg/scss/components/_nav.scss @@ -0,0 +1,31 @@ +.nav { + display: flex; + justify-content: space-between; + + &__list { + display: flex; + list-style: none; + font-size: 1rem; + color: $color-white; + margin: 0; + gap: $grid-margin-3; + } + + &__item { + color: $color-white; + } + + &__item--active &__link { + color: $color_accent; + text-decoration: underline; + } + + &__link { + color: $color-white; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/src/sdg/scss/components/_user-dropdown.scss b/src/sdg/scss/components/_user-dropdown.scss new file mode 100644 index 000000000..f3e48b54f --- /dev/null +++ b/src/sdg/scss/components/_user-dropdown.scss @@ -0,0 +1,86 @@ +.user-dropdown { + --shadow-color: rgba(0, 0, 0, 0.121722); + --border-color: #{$color_grey_dark}; + + align-items: center; + background-color: var(--org-theme-bg-darker); + border: 1px solid transparent; + border-radius: 9999px; + color: $color-white; + display: flex; + gap: $grid-margin-0; + height: 40px; + text-decoration: none; + padding-right: $grid-margin-2; + position: relative; + transition: all 0.3s ease-in-out; + + &:hover &__list, + & > *:hover ~ &__list, + &__list:hover { + opacity: 1; + transition-delay: 0s; + visibility: visible; + } + + &:hover { + background-color: $color_grey_light; + border-color: var(--border-color); + color: #212121; + } + + &__icon { + background-color: $color_primary_lightest; + border: 2px solid var(--border-color); + border-radius: 50%; + color: $color_primary; + flex-shrink: 0; + height: 50px; + left: -4px; + overflow: hidden; + padding-top: 4px; + position: relative; + width: 50px; + + > .svg-inline--fa { + color: $color_primary; + height: 100%; + width: 100%; + } + } + + &__list { + background-color: $color_grey_light; + border: 1px solid var(--border-color); + border-radius: 10px; + box-shadow: 1px 1px 4px var(--shadow-color); + color: #fff; + left: 0; + opacity: 0; + position: absolute; + right: 0; + top: calc(100% + 1rem); + transition: all 0.3s ease-in-out; + transition-delay: 0s; + visibility: hidden; + z-index: 20; + } + + &__action { + align-items: center; + color: $color_primary_dark; + display: flex; + gap: 0.5rem; + padding: 12px; + text-align: left; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + + hr { + border: 0.5px solid var(--border-color); + } +} diff --git a/src/sdg/templates/core/_municipality_switch.html b/src/sdg/templates/core/_municipality_switch.html deleted file mode 100644 index 7c8cc7597..000000000 --- a/src/sdg/templates/core/_municipality_switch.html +++ /dev/null @@ -1,7 +0,0 @@ -{% load i18n %} - -{% if user.roles.count > 1 %} - - - -{% endif %} diff --git a/src/sdg/templates/core/base_home.html b/src/sdg/templates/core/base_home.html index 136df1b56..81be3c80a 100644 --- a/src/sdg/templates/core/base_home.html +++ b/src/sdg/templates/core/base_home.html @@ -17,75 +17,10 @@

voor {{ org_type_cfg.name_plural }}

-
- - - - {{ request.user }} - - -
- {% with pk=lokaleoverheid.pk %} - - {% if not pk %} - - {% trans "Selecteer eerst een organisatie voor meer opties." %} - - {% else %} - {% if request.user|is_manager:lokaleoverheid %} - - {% trans "Organisatie instellingen" %} - - - {% trans "Locaties" %} - - - {% trans "Bevoegde organisaties" %} - - {% endif %} - - {% trans "Gebruikersbeheer" %} - - {% endif %} - - {% if siteconfig.documentatie_link %} - - {{ siteconfig.documentatie_titel|capfirst|default:_("Documentatie") }} - - {% endif %} - -
- - - {% trans "Notificaties" %} - - -
- - - {% trans "Uitloggen" %} - - - {% endwith %} -
-
+ {% user_dropdown %}
-

{% block container_subtitle %}{% endblock container_subtitle %}

-

- {% block container_title %} - {% if lokaleoverheid %} - {{ lokaleoverheid }} - {% include "core/_municipality_switch.html" %} - {% else %} -

- SDG {% trans "Invoervoorziening" %} -
- {% endif %} - {% endblock container_title %} -

+ {% navigation %}
{% endblock %} diff --git a/src/sdg/templates/core/index.html b/src/sdg/templates/core/index.html index ec74e9c8b..6017b32f0 100644 --- a/src/sdg/templates/core/index.html +++ b/src/sdg/templates/core/index.html @@ -5,12 +5,6 @@ {% block head_title %}{% trans "Home" %}{% endblock %} -{% block container_title %} -
- SDG {% trans "Invoervoorziening" %} -
-{% endblock container_title %} - {% block inner %}
diff --git a/src/sdg/templates/navigation/nav_item.html b/src/sdg/templates/navigation/nav_item.html new file mode 100644 index 000000000..bf1ed9360 --- /dev/null +++ b/src/sdg/templates/navigation/nav_item.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/src/sdg/templates/navigation/navigation.html b/src/sdg/templates/navigation/navigation.html new file mode 100644 index 000000000..fbf3f950b --- /dev/null +++ b/src/sdg/templates/navigation/navigation.html @@ -0,0 +1,43 @@ +{% load i18n utils %} + +{% with pk=lokaleoverheid.pk %} + +{% endwith %} \ No newline at end of file diff --git a/src/sdg/templates/navigation/user_dropdown.html b/src/sdg/templates/navigation/user_dropdown.html new file mode 100644 index 000000000..d47f9a3f7 --- /dev/null +++ b/src/sdg/templates/navigation/user_dropdown.html @@ -0,0 +1,23 @@ +{% load i18n %} +
+
+ +
+ +

{{ request.user }}

+ +
+ {% if lokaleoverheid.pk %} + + {% trans "Mijn instellingen" %} + +
+ {% endif %} + + {% trans "Uitloggen" %} + +
+
diff --git a/src/sdg/utils/context_processors.py b/src/sdg/utils/context_processors.py index 9811c069c..c88cdf5b6 100644 --- a/src/sdg/utils/context_processors.py +++ b/src/sdg/utils/context_processors.py @@ -1,6 +1,7 @@ from django.conf import settings as django_settings from sdg.conf.utils import org_type_cfg +from sdg.producten.models import NotificationViewed, ProductVersie def settings(request): @@ -25,3 +26,34 @@ def settings(request): context.update(dsn=django_settings.SENTRY_CONFIG.get("public_dsn", "")) return context + + +def has_new_notifications(request): + if request.user and request.user.is_anonymous != True: + # Get the user's NotificationViewed instance + notification_viewed = NotificationViewed.objects.get(gebruiker=request.user) + + # Get the latest product version after the last_viewed_date else None. + latest_notification = ( + ProductVersie.objects.filter( + product__referentie_product=None, + gewijzigd_op__gt=notification_viewed.last_viewed_date, + ) + .order_by("-gewijzigd_op") + .first() + ) + + # Check if there is a new notification + has_new = latest_notification is not None + + return { + "has_new_notifications": has_new, + "latest_notification_date": ( + latest_notification.gewijzigd_op if latest_notification else None + ), + } + + return { + "has_new_notifications": False, + "latest_notification_date": None, + } diff --git a/src/sdg/utils/templatetags/utils.py b/src/sdg/utils/templatetags/utils.py index a54de4372..635f3f140 100644 --- a/src/sdg/utils/templatetags/utils.py +++ b/src/sdg/utils/templatetags/utils.py @@ -2,8 +2,6 @@ from django.conf import settings from django.utils.html import format_html -from sdg.producten.types import Language - register = template.Library() @@ -141,3 +139,99 @@ def is_manager(user, local_government): ] ) return None + + +@register.inclusion_tag("navigation/navigation.html", takes_context=True) +def navigation(context): + """ + Navigation element. + + Args: + - context + """ + + lokaleoverheid = context.get("lokaleoverheid") + request = context.get("request") + siteconfig = context.get("siteconfig") + has_new_notifications = context.get("has_new_notifications") + + return { + "context": context, + "lokaleoverheid": lokaleoverheid, + "request": request, + "siteconfig": siteconfig, + "has_new_notifications": has_new_notifications, + } + + +@register.inclusion_tag("navigation/nav_item.html", takes_context=True) +def nav_item(context, href, title, **kwargs): + """ + Generic nav_item element, built for the navigation component. + + Args: + - context + - link, href of the link + - title, label of the link (can also be an element) + + Kwargs: + - icon, can be an element. + - id, set an id on the element + - blank_target, set the target to `_blank` + """ + + request = context.get("request") + + def check_active_link(): + if href == "/": + # Equality operator instead of partial check (disables always true on home route) + return href == request.path + else: + return href in request.path + + # Validate if the link is active. + active_link = check_active_link() + + # Get kwargs vars. + icon = kwargs.get("icon", None) + id = kwargs.get("id", None) + blank_target = kwargs.get("blank_target", False) + + return { + **kwargs, + "context": context, + "href": href, + "title": title, + "icon": icon, + "blank_target": blank_target, + "id": id, + "active_link": active_link, + } + + +@register.inclusion_tag("navigation/user_dropdown.html", takes_context=True) +def user_dropdown(context): + """ + Dropdown element inside the header. + + Args: + - context + """ + + lokaleoverheid = context.get("lokaleoverheid") + request = context.get("request") + + role_pk = None + if lokaleoverheid: + role_pk = ( + request.user.roles.all() + .get(lokale_overheid=lokaleoverheid, user=request.user) + .pk + ) + + return { + "context": context, + "lokaleoverheid": lokaleoverheid, + "request": request, + "role_pk": role_pk, + } From 81ffdab8d8046986954ba7889e4cfb486ec88022 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 21 Oct 2024 18:14:41 +0200 Subject: [PATCH 2/7] :green_heart: FIX CI build --- src/sdg/utils/context_processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdg/utils/context_processors.py b/src/sdg/utils/context_processors.py index c88cdf5b6..7c0207897 100644 --- a/src/sdg/utils/context_processors.py +++ b/src/sdg/utils/context_processors.py @@ -29,7 +29,7 @@ def settings(request): def has_new_notifications(request): - if request.user and request.user.is_anonymous != True: + if request.user and request.user.is_anonymous is not True: # Get the user's NotificationViewed instance notification_viewed = NotificationViewed.objects.get(gebruiker=request.user) From 4e62c96ffc918ded31935ba5a08bfbcece7c287a Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 22 Oct 2024 09:23:34 +0200 Subject: [PATCH 3/7] :green_heart: FIX CI build by adding better errorhandling --- src/sdg/utils/context_processors.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sdg/utils/context_processors.py b/src/sdg/utils/context_processors.py index 7c0207897..3f59bdf74 100644 --- a/src/sdg/utils/context_processors.py +++ b/src/sdg/utils/context_processors.py @@ -2,6 +2,8 @@ from sdg.conf.utils import org_type_cfg from sdg.producten.models import NotificationViewed, ProductVersie +from django.utils.timezone import now +from dateutil.relativedelta import relativedelta def settings(request): @@ -31,13 +33,25 @@ def settings(request): def has_new_notifications(request): if request.user and request.user.is_anonymous is not True: # Get the user's NotificationViewed instance - notification_viewed = NotificationViewed.objects.get(gebruiker=request.user) + try: + notification_viewed = NotificationViewed.objects.get(gebruiker=request.user) + except NotificationViewed.DoesNotExist: + notification_viewed = None + + + # Get the last_viewed_date from data + try: + last_viewed_date = notification_viewed.last_viewed_date + except AttributeError: + # default last_viewed_date is 12 months ago + last_viewed_date = now() - relativedelta(months=12) + # Get the latest product version after the last_viewed_date else None. latest_notification = ( ProductVersie.objects.filter( product__referentie_product=None, - gewijzigd_op__gt=notification_viewed.last_viewed_date, + gewijzigd_op__gt=last_viewed_date, ) .order_by("-gewijzigd_op") .first() From 0503625d9225ea1744b851873dbda047a351f26b Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 22 Oct 2024 09:28:46 +0200 Subject: [PATCH 4/7] :green_heart: FIX CI build --- src/sdg/utils/context_processors.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sdg/utils/context_processors.py b/src/sdg/utils/context_processors.py index 3f59bdf74..af780f3bd 100644 --- a/src/sdg/utils/context_processors.py +++ b/src/sdg/utils/context_processors.py @@ -1,9 +1,10 @@ from django.conf import settings as django_settings +from django.utils.timezone import now + +from dateutil.relativedelta import relativedelta from sdg.conf.utils import org_type_cfg from sdg.producten.models import NotificationViewed, ProductVersie -from django.utils.timezone import now -from dateutil.relativedelta import relativedelta def settings(request): @@ -38,7 +39,6 @@ def has_new_notifications(request): except NotificationViewed.DoesNotExist: notification_viewed = None - # Get the last_viewed_date from data try: last_viewed_date = notification_viewed.last_viewed_date @@ -46,7 +46,6 @@ def has_new_notifications(request): # default last_viewed_date is 12 months ago last_viewed_date = now() - relativedelta(months=12) - # Get the latest product version after the last_viewed_date else None. latest_notification = ( ProductVersie.objects.filter( From 5d91833ada9f8295d9893fd566a3f27c93556ad0 Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 18 Nov 2024 15:03:47 +0100 Subject: [PATCH 5/7] :recycle: [#990] Refactor some code based on feedback --- src/sdg/organisaties/views/notificaties.py | 10 ++---- ...ter_notificationviewed_last_viewed_date.py | 18 ++++++++++ src/sdg/producten/models/notification.py | 7 ++-- src/sdg/templates/navigation/nav_item.html | 2 +- src/sdg/templates/navigation/navigation.html | 6 +--- src/sdg/utils/context_processors.py | 34 +++++++------------ src/sdg/utils/templatetags/utils.py | 3 ++ 7 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 src/sdg/producten/migrations/0060_alter_notificationviewed_last_viewed_date.py diff --git a/src/sdg/organisaties/views/notificaties.py b/src/sdg/organisaties/views/notificaties.py index f012d3751..5138ad5ce 100644 --- a/src/sdg/organisaties/views/notificaties.py +++ b/src/sdg/organisaties/views/notificaties.py @@ -47,13 +47,7 @@ def get(self, request, *args, **kwargs): # Call the parent class's get method to fetch the queryset response = super().get(request, *args, **kwargs) - # Get or create the NotificationViewed instance for the current user - notification_viewed, create = NotificationViewed.objects.get_or_create( - gebruiker=request.user - ) - - # Update the last_viewed_date to the current time - notification_viewed.last_viewed_date = timezone.now() - notification_viewed.save() + # Update or create the NotificationViewed instance for the current user + NotificationViewed.objects.update_or_create(gebruiker=request.user) return response diff --git a/src/sdg/producten/migrations/0060_alter_notificationviewed_last_viewed_date.py b/src/sdg/producten/migrations/0060_alter_notificationviewed_last_viewed_date.py new file mode 100644 index 000000000..a91b7a576 --- /dev/null +++ b/src/sdg/producten/migrations/0060_alter_notificationviewed_last_viewed_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2024-11-15 16:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("producten", "0059_notificationviewed"), + ] + + operations = [ + migrations.AlterField( + model_name="notificationviewed", + name="last_viewed_date", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/src/sdg/producten/models/notification.py b/src/sdg/producten/models/notification.py index 701cc4ecf..c1286e92e 100644 --- a/src/sdg/producten/models/notification.py +++ b/src/sdg/producten/models/notification.py @@ -1,15 +1,12 @@ from django.contrib.auth import get_user_model from django.db import models -from django.utils import timezone User = get_user_model() class NotificationViewed(models.Model): gebruiker = models.OneToOneField(User, on_delete=models.CASCADE) - last_viewed_date = models.DateTimeField(null=True, blank=True) + last_viewed_date = models.DateTimeField(auto_now=True) def __str__(self): - return ( - f"{self.gebruiker} - Notification last viewed on: {self.last_viewed_date}" - ) + return f"User: {self.gebruiker_id} - Notification last viewed on: {self.last_viewed_date}" diff --git a/src/sdg/templates/navigation/nav_item.html b/src/sdg/templates/navigation/nav_item.html index bf1ed9360..4bc95ede7 100644 --- a/src/sdg/templates/navigation/nav_item.html +++ b/src/sdg/templates/navigation/nav_item.html @@ -6,6 +6,6 @@ {% if blank_target %}target="_blank" rel="noopener"{% endif %} > {{title}} - {% if icon %}{{icon}}{% endif %} + {% if icon and show_icon %}{{icon}}{% endif %} \ No newline at end of file diff --git a/src/sdg/templates/navigation/navigation.html b/src/sdg/templates/navigation/navigation.html index fbf3f950b..f0a99aaf3 100644 --- a/src/sdg/templates/navigation/navigation.html +++ b/src/sdg/templates/navigation/navigation.html @@ -29,11 +29,7 @@ {# Show new notification icon if has_new_notifications is True #} {% url 'notificaties' as notification_url %} - {% if has_new_notifications %} - {% nav_item title=_('Notificaties') href=notification_url icon='' %} - {% else %} - {% nav_item title=_('Notificaties') href=notification_url %} - {% endif %} + {% nav_item title=_('Notificaties') href=notification_url icon='' show_icon=has_new_notifications %} {% if siteconfig.documentatie_link %} {% nav_item title=siteconfig.documentatie_titel|capfirst|default:_("Documentatie") href=siteconfig.documentatie_link blank_target=True id="documentation" %} diff --git a/src/sdg/utils/context_processors.py b/src/sdg/utils/context_processors.py index af780f3bd..386a07d8a 100644 --- a/src/sdg/utils/context_processors.py +++ b/src/sdg/utils/context_processors.py @@ -32,23 +32,22 @@ def settings(request): def has_new_notifications(request): - if request.user and request.user.is_anonymous is not True: - # Get the user's NotificationViewed instance - try: - notification_viewed = NotificationViewed.objects.get(gebruiker=request.user) - except NotificationViewed.DoesNotExist: - notification_viewed = None + user = request.user + has_new_notifications = False - # Get the last_viewed_date from data + if user and user.is_anonymous is not True: try: + notification_viewed = NotificationViewed.objects.get(gebruiker=user) last_viewed_date = notification_viewed.last_viewed_date - except AttributeError: - # default last_viewed_date is 12 months ago + except NotificationViewed.DoesNotExist: + notification_viewed = None + # By default, the last_viewed_date is set to 12 months ago. last_viewed_date = now() - relativedelta(months=12) - # Get the latest product version after the last_viewed_date else None. + # Get the latest product versie (notifications are based on ProductVersie) later than last_viewed_date. latest_notification = ( - ProductVersie.objects.filter( + ProductVersie.objects.select_related("product") + .filter( product__referentie_product=None, gewijzigd_op__gt=last_viewed_date, ) @@ -56,17 +55,8 @@ def has_new_notifications(request): .first() ) - # Check if there is a new notification - has_new = latest_notification is not None - - return { - "has_new_notifications": has_new, - "latest_notification_date": ( - latest_notification.gewijzigd_op if latest_notification else None - ), - } + has_new_notifications = bool(latest_notification) return { - "has_new_notifications": False, - "latest_notification_date": None, + "has_new_notifications": has_new_notifications, } diff --git a/src/sdg/utils/templatetags/utils.py b/src/sdg/utils/templatetags/utils.py index 635f3f140..bf54c53f6 100644 --- a/src/sdg/utils/templatetags/utils.py +++ b/src/sdg/utils/templatetags/utils.py @@ -178,6 +178,7 @@ def nav_item(context, href, title, **kwargs): - icon, can be an element. - id, set an id on the element - blank_target, set the target to `_blank` + - show_icon, boolean to render the icon """ request = context.get("request") @@ -194,6 +195,7 @@ def check_active_link(): # Get kwargs vars. icon = kwargs.get("icon", None) + show_icon = kwargs.get("show_icon", None) id = kwargs.get("id", None) blank_target = kwargs.get("blank_target", False) @@ -203,6 +205,7 @@ def check_active_link(): "href": href, "title": title, "icon": icon, + "show_icon": show_icon, "blank_target": blank_target, "id": id, "active_link": active_link, From e8cb15b9d1741c91c9bbec3ab846733ab01a932c Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 18 Nov 2024 17:47:43 +0100 Subject: [PATCH 6/7] :adhesive_bandage: FIX redactor user can access list through 'annuleren' button --- src/sdg/templates/organisaties/roles/update.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/sdg/templates/organisaties/roles/update.html b/src/sdg/templates/organisaties/roles/update.html index 299bfe5ec..e10faca3c 100644 --- a/src/sdg/templates/organisaties/roles/update.html +++ b/src/sdg/templates/organisaties/roles/update.html @@ -23,7 +23,12 @@
From abc9b478e48aeee1a2f67978608d42957c319642 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 19 Nov 2024 11:01:04 +0100 Subject: [PATCH 7/7] :adhesive_bandage: [#990] FIX wrong user redirect on form submit. --- src/sdg/organisaties/views/roles.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sdg/organisaties/views/roles.py b/src/sdg/organisaties/views/roles.py index cfcabd969..705622a08 100644 --- a/src/sdg/organisaties/views/roles.py +++ b/src/sdg/organisaties/views/roles.py @@ -79,6 +79,15 @@ def get_form_class(self): raise PermissionDenied() + def get_success_url(self): + # Stay on the same page if the user is editing own settings and is not an admin. + if ( + self.request.user.email == self.object.user.email + and self.object.is_beheerder is not True + ): + return self.request.get_full_path() + return super().get_success_url() + def form_valid(self, form, *args, **kwargs): response = super().form_valid(form)