From efe417b1fb69639e19082a9c83179df6d8923e83 Mon Sep 17 00:00:00 2001 From: Hanne Moa Date: Mon, 16 Dec 2024 09:51:33 +0100 Subject: [PATCH 1/6] Add rudimentary notificationprofile support --- ...mentary-notificationprofiles-page.added.md | 1 + src/argus/htmx/notificationprofile/urls.py | 22 ++--- src/argus/htmx/notificationprofile/views.py | 82 +++++++++++++++++++ src/argus/htmx/templates/htmx/base.html | 2 +- .../_notificationprofile_detail.html | 25 ++++++ .../notificationprofile_confirm_delete.html | 9 ++ .../notificationprofile_detail.html | 6 ++ .../notificationprofile_form.html | 9 ++ .../notificationprofile_list.html | 24 ++++++ 9 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 changelog.d/+add-rudimentary-notificationprofiles-page.added.md create mode 100644 src/argus/htmx/notificationprofile/views.py create mode 100644 src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html create mode 100644 src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_confirm_delete.html create mode 100644 src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_detail.html create mode 100644 src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html create mode 100644 src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_list.html diff --git a/changelog.d/+add-rudimentary-notificationprofiles-page.added.md b/changelog.d/+add-rudimentary-notificationprofiles-page.added.md new file mode 100644 index 000000000..2374d1bb5 --- /dev/null +++ b/changelog.d/+add-rudimentary-notificationprofiles-page.added.md @@ -0,0 +1 @@ +Replaced the placeholder notification profile page with a very ugly but functional one. diff --git a/src/argus/htmx/notificationprofile/urls.py b/src/argus/htmx/notificationprofile/urls.py index 0d9ee8d13..c3867c1ff 100644 --- a/src/argus/htmx/notificationprofile/urls.py +++ b/src/argus/htmx/notificationprofile/urls.py @@ -1,23 +1,13 @@ -from django.http import HttpResponse -from django.template import Template, RequestContext from django.urls import path -from django.views.decorators.http import require_GET - -@require_GET -def placeholder(request): - template = Template( - """{% extends "htmx/base.html" %} - {% block main %} -

NOTIFICATION PROFILES PLACEHOLDER

- {% endblock main %} - """ - ) - context = RequestContext(request) - return HttpResponse(template.render(context)) +from . import views app_name = "htmx" urlpatterns = [ - path("", placeholder, name="notificationprofile-placeholder"), + path("", views.NotificationProfileListView.as_view(), name="notificationprofile-list"), + path("create/", views.NotificationProfileCreateView.as_view(), name="notificationprofile-create"), + path("/", views.NotificationProfileDetailView.as_view(), name="notificationprofile-detail"), + path("/update/", views.NotificationProfileUpdateView.as_view(), name="notificationprofile-update"), + path("/delete/", views.NotificationProfileDeleteView.as_view(), name="notificationprofile-delete"), ] diff --git a/src/argus/htmx/notificationprofile/views.py b/src/argus/htmx/notificationprofile/views.py new file mode 100644 index 000000000..31d44c05d --- /dev/null +++ b/src/argus/htmx/notificationprofile/views.py @@ -0,0 +1,82 @@ +from django import forms +from django.urls import reverse +from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView + +from argus.notificationprofile.models import NotificationProfile, Timeslot, Filter, DestinationConfig + + +class NotificationProfileForm(forms.ModelForm): + class Meta: + model = NotificationProfile + fields = ["name", "timeslot", "filters", "active", "destinations"] + + def __init__(self, *args, **kwargs): + user = kwargs.pop("user") + super().__init__(*args, **kwargs) + self.fields["timeslot"].queryset = Timeslot.objects.filter(user=user) + self.fields["filters"].queryset = Filter.objects.filter(user=user) + self.fields["destinations"].queryset = DestinationConfig.objects.filter(user=user) + + +class NotificationProfileMixin: + model = NotificationProfile + + def get_queryset(self): + qs = ( + super() + .get_queryset() + .select_related("timeslot") + .prefetch_related( + "filters", + "destinations", + ) + ) + return qs.filter(user_id=self.request.user.id) + + def get_template_names(self): + orig_app_label = self.model._meta.app_label + orig_model_name = self.model._meta.model_name + self.model._meta.app_label = "htmx/notificationprofile" + self.model._meta.model_name = "notificationprofile" + templates = super().get_template_names() + self.model._meta.app_label = orig_app_label + self.model._meta.model_name = orig_model_name + return templates + + def get_success_url(self): + return reverse("htmx:notificationprofile-list") + + +class ChangeMixin: + form_class = NotificationProfileForm + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["user"] = self.request.user + return kwargs + + def form_valid(self, form): + self.object = form.save(commit=False) + self.object.user = self.request.user + self.object.save() + return super().form_valid(form) + + +class NotificationProfileListView(NotificationProfileMixin, ListView): + pass + + +class NotificationProfileDetailView(NotificationProfileMixin, DetailView): + pass + + +class NotificationProfileCreateView(ChangeMixin, NotificationProfileMixin, CreateView): + pass + + +class NotificationProfileUpdateView(ChangeMixin, NotificationProfileMixin, UpdateView): + pass + + +class NotificationProfileDeleteView(NotificationProfileMixin, DeleteView): + pass diff --git a/src/argus/htmx/templates/htmx/base.html b/src/argus/htmx/templates/htmx/base.html index 749867cf4..456e12c49 100644 --- a/src/argus/htmx/templates/htmx/base.html +++ b/src/argus/htmx/templates/htmx/base.html @@ -22,7 +22,7 @@ Timeslots
  • - Profiles + Profiles
  • {% endblock navlinks %} diff --git a/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html b/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html new file mode 100644 index 000000000..500cdb4b1 --- /dev/null +++ b/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html @@ -0,0 +1,25 @@ +
    +

    {{ object.name|default:object.timeslot.name }}

    +
    +

    Active? {{ object.active }}

    +

    Timeslot: {{ object.timeslot }}

    +

    + Filters: + {% for filter_obj in object.filters.all %}{{ filter_obj }}{% endfor %} +

    +

    + Destinations: + {% for destination in object.destinations.all %}{{ destination }}{% endfor %} +

    + {% if show_buttons %} +

    + Update +

    +

    + Delete +

    + {% endif %} +
    +
    diff --git a/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_confirm_delete.html b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_confirm_delete.html new file mode 100644 index 000000000..9e9eaba2c --- /dev/null +++ b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_confirm_delete.html @@ -0,0 +1,9 @@ +{% extends "htmx/base.html" %} +{% block main %} +
    + {% csrf_token %} +

    Are you sure you want to delete "{{ object }}"?

    + {{ form }} + +
    +{% endblock main %} diff --git a/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_detail.html b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_detail.html new file mode 100644 index 000000000..912f56326 --- /dev/null +++ b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_detail.html @@ -0,0 +1,6 @@ +{% extends "htmx/base.html" %} +{% block main %} + {% with show_buttons=True %} + {% include "./_notificationprofile_detail.html" %} + {% endwith %} +{% endblock main %} diff --git a/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html new file mode 100644 index 000000000..19b1e34d7 --- /dev/null +++ b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html @@ -0,0 +1,9 @@ +{% extends "htmx/base.html" %} +{% block main %} +
    + {% csrf_token %} + {{ form.as_div }} + {{ formset.as_div }} + +
    +{% endblock main %} diff --git a/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_list.html b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_list.html new file mode 100644 index 000000000..ea414290d --- /dev/null +++ b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_list.html @@ -0,0 +1,24 @@ +{% extends "htmx/base.html" %} +{% block main %} +

    + Create +

    + {% for object in object_list %} +
    + {% include "./_notificationprofile_detail.html" %} +

    + View +

    +

    + Update +

    +

    + Delete +

    +
    + {% endfor %} +{% endblock main %} From 93945b426c7f43b2aef22024ae13b554030a9e30 Mon Sep 17 00:00:00 2001 From: Hanne Moa Date: Wed, 18 Dec 2024 11:21:02 +0100 Subject: [PATCH 2/6] Update src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html Co-authored-by: podliashanyk <60876078+podliashanyk@users.noreply.github.com> --- .../htmx/notificationprofile/_notificationprofile_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html b/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html index 500cdb4b1..96a542c6a 100644 --- a/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html +++ b/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html @@ -14,7 +14,7 @@

    {{ object.name|default:object.timeslot.name }}

    {% if show_buttons %}

    Update + href="{% url "htmx:notificationprofile-update" pk=object.pk %}">Update

    Date: Wed, 18 Dec 2024 11:21:14 +0100 Subject: [PATCH 3/6] Update src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html Co-authored-by: podliashanyk <60876078+podliashanyk@users.noreply.github.com> --- .../htmx/notificationprofile/_notificationprofile_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html b/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html index 96a542c6a..f16d814bd 100644 --- a/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html +++ b/src/argus/htmx/templates/htmx/notificationprofile/_notificationprofile_detail.html @@ -18,7 +18,7 @@

    {{ object.name|default:object.timeslot.name }}

    Delete + href="{% url "htmx:notificationprofile-delete" pk=object.pk %}">Delete

    {% endif %} From db8cc7732e340e3ca30929b397ab96bb27638c72 Mon Sep 17 00:00:00 2001 From: Hanne Moa Date: Wed, 18 Dec 2024 11:25:54 +0100 Subject: [PATCH 4/6] add dosctrings --- src/argus/htmx/notificationprofile/views.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/argus/htmx/notificationprofile/views.py b/src/argus/htmx/notificationprofile/views.py index 31d44c05d..fe755c7cf 100644 --- a/src/argus/htmx/notificationprofile/views.py +++ b/src/argus/htmx/notificationprofile/views.py @@ -1,3 +1,9 @@ +""" +Everything needed python-wise to CRUD notificationprofiles + +See https://ccbv.co.uk/ to grok class-based views. +""" + from django import forms from django.urls import reverse from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView @@ -19,6 +25,8 @@ def __init__(self, *args, **kwargs): class NotificationProfileMixin: + "Common functionality for all views" + model = NotificationProfile def get_queryset(self): @@ -48,6 +56,8 @@ def get_success_url(self): class ChangeMixin: + "Common functionality for create and update views" + form_class = NotificationProfileForm def get_form_kwargs(self): From e9078ca70b223bfa085c625ce46dd54833492645 Mon Sep 17 00:00:00 2001 From: Hanne Moa Date: Wed, 18 Dec 2024 11:52:25 +0100 Subject: [PATCH 5/6] update src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html Co-authored-by: podliashanyk <60876078+podliashanyk@users.noreply.github.com> --- .../htmx/notificationprofile/notificationprofile_form.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html index 19b1e34d7..6b8208b43 100644 --- a/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html +++ b/src/argus/htmx/templates/htmx/notificationprofile/notificationprofile_form.html @@ -3,7 +3,6 @@
    {% csrf_token %} {{ form.as_div }} - {{ formset.as_div }}
    {% endblock main %} From 6ebbba5b01996ebee892ec3b31e1f47b96c2f793 Mon Sep 17 00:00:00 2001 From: Hanne Moa Date: Wed, 18 Dec 2024 11:53:48 +0100 Subject: [PATCH 6/6] update styles --- src/argus/htmx/static/styles.css | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/argus/htmx/static/styles.css b/src/argus/htmx/static/styles.css index 3fe7244e2..3e7da72c9 100644 --- a/src/argus/htmx/static/styles.css +++ b/src/argus/htmx/static/styles.css @@ -936,7 +936,38 @@ html { color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); } +.breadcrumbs { + max-width: 100%; + overflow-x: auto; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.breadcrumbs > ul, + .breadcrumbs > ol { + display: flex; + align-items: center; + white-space: nowrap; + min-height: -moz-min-content; + min-height: min-content; +} + +.breadcrumbs > ul > li, .breadcrumbs > ol > li { + display: flex; + align-items: center; +} + +.breadcrumbs > ul > li > a, .breadcrumbs > ol > li > a { + display: flex; + cursor: pointer; + align-items: center; +} + @media (hover:hover) { + .breadcrumbs > ul > li > a:hover, .breadcrumbs > ol > li > a:hover { + text-decoration-line: underline; + } + .checkbox-primary:hover { --tw-border-opacity: 1; border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); @@ -1304,6 +1335,16 @@ html { opacity: 1; } + .btm-nav > *.disabled:hover, + .btm-nav > *[disabled]:hover { + pointer-events: none; + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } + .btn:hover { --tw-border-opacity: 1; border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); @@ -2097,11 +2138,57 @@ input.tab:checked + .tab-content, color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity))); } +.btm-nav > *:where(.active) { + border-top-width: 2px; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.btm-nav > *.disabled, + .btm-nav > *[disabled] { + pointer-events: none; + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + .btm-nav > * .label { font-size: 1rem; line-height: 1.5rem; } +.breadcrumbs > ul > li > a:focus, .breadcrumbs > ol > li > a:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.breadcrumbs > ul > li > a:focus-visible, .breadcrumbs > ol > li > a:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +.breadcrumbs > ul > li + *:before, .breadcrumbs > ol > li + *:before { + content: ""; + margin-left: 0.5rem; + margin-right: 0.75rem; + display: block; + height: 0.375rem; + width: 0.375rem; + --tw-rotate: 45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + opacity: 0.4; + border-top: 1px solid; + border-right: 1px solid; + background-color: transparent; +} + +[dir="rtl"] .breadcrumbs > ul > li + *:before, +[dir="rtl"] .breadcrumbs > ol > li + *:before { + --tw-rotate: -135deg; +} + @media (prefers-reduced-motion: no-preference) { .btn { animation: button-pop var(--animation-btn, 0.25s) ease-out; @@ -3321,6 +3408,13 @@ details.collapse summary::-webkit-details-marker { background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } +.table-zebra tr.active, + .table-zebra tr.active:nth-child(even), + .table-zebra-zebra tbody tr:nth-child(even) { + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); +} + .table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) { border-bottom-width: 1px; --tw-border-opacity: 1; @@ -3451,6 +3545,22 @@ details.collapse summary::-webkit-details-marker { } } +.btm-nav-xs > *:where(.active) { + border-top-width: 1px; +} + +.btm-nav-sm > *:where(.active) { + border-top-width: 2px; +} + +.btm-nav-md > *:where(.active) { + border-top-width: 2px; +} + +.btm-nav-lg > *:where(.active) { + border-top-width: 4px; +} + .btn-xs { height: 1.5rem; min-height: 1.5rem; @@ -4283,6 +4393,10 @@ details.collapse summary::-webkit-details-marker { flex-grow: 1; } +.border-collapse { + border-collapse: collapse; +} + .border-separate { border-collapse: separate; }