mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-11-05 05:23:06 +00:00
[CHANGE] Beautify Admin Announcements
This commit is contained in:
3 files changed
+112
-29
No files matched your search
@@ -46,36 +46,41 @@ the escapejs filter without having to redefine them later.
|
||||
{% translate "Announcements" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div>
|
||||
<ul class="list-group">
|
||||
{% for notif in notifications %}
|
||||
{% if not notif.is_hidden %}
|
||||
<li class="list-group-item">
|
||||
<span class="badge text-bg-info me-2">{{ notif.application_name }}</span>
|
||||
<a href="{{ notif.announcement_url }}" target="_blank" rel="noopener noreferrer">{{ notif.announcement_text }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div class="alert alert-primary" role="alert">
|
||||
{% translate "No notifications at this time" %}
|
||||
<div class="row">
|
||||
{% with apps_count=notifications|length %}
|
||||
{% for application in notifications %}
|
||||
<div class="{% if apps_count >= 2 %}col-md-6{% endif %} {% if apps_count >= 3 %}col-lg-4{% endif %}">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ application.application_name }}</h5>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for announcement in application.announcements %}
|
||||
<li class="list-group-item">
|
||||
<a href="{{ announcement.announcement_url }}" target="_blank" rel="noopener noreferrer">{{ announcement.announcement_text }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="text-end pt-3">
|
||||
<a href="https://gitlab.com/allianceauth/allianceauth/issues" target="_blank" class="me-1 text-decoration-none">
|
||||
<span class="badge text-bg-danger">
|
||||
<i class="fab fa-gitlab" aria-hidden="true"></i>
|
||||
{% translate 'Powered by GitLab' %}
|
||||
</span>
|
||||
</a>
|
||||
<a href="https://discord.com/invite/fjnHAmk" target="_blank" class="text-decoration-none">
|
||||
<span class="badge text-bg-info">
|
||||
<i class="fab fa-discord" aria-hidden="true"></i>
|
||||
{% translate 'Support Discord' %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-end pt-3">
|
||||
<a href="https://gitlab.com/allianceauth/allianceauth/issues" target="_blank" class="me-1 text-decoration-none">
|
||||
<span class="badge text-bg-danger">
|
||||
<i class="fab fa-gitlab" aria-hidden="true"></i>
|
||||
{% translate 'Powered by GitLab' %}
|
||||
</span>
|
||||
</a>
|
||||
<a href="https://discord.com/invite/fjnHAmk" target="_blank" class="text-decoration-none">
|
||||
<span class="badge text-bg-info">
|
||||
<i class="fab fa-discord" aria-hidden="true"></i>
|
||||
{% translate 'Support Discord' %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ def _current_notifications() -> dict:
|
||||
|
||||
# Ensure "Alliance Auth" appears first, then the rest alphabetically.
|
||||
# We annotate a custom ordering field: 0 for Alliance Auth, 1 for others.
|
||||
application_notifications = (
|
||||
application_announcements_qs = (
|
||||
ApplicationAnnouncement.objects.annotate(
|
||||
order_priority=Case(
|
||||
When(application_name="Alliance Auth", then=Value(0)),
|
||||
@@ -83,9 +83,43 @@ def _current_notifications() -> dict:
|
||||
.order_by("order_priority", "application_name", "-announcement_number")
|
||||
)
|
||||
|
||||
# change the dict format to a list of dicts for easier template rendering
|
||||
# `application_notifications = [{"application_name": "", "announcements": []}]`
|
||||
# Build groups in Python to ensure we preserve the queryset order and avoid
|
||||
# duplicates regardless of DB distinct behavior. This also avoids extra
|
||||
# per-application queries by iterating the queryset once.
|
||||
grouped = {}
|
||||
ordered_names = []
|
||||
|
||||
for announcement in application_announcements_qs:
|
||||
name = announcement.application_name
|
||||
|
||||
if name not in grouped:
|
||||
grouped[name] = []
|
||||
ordered_names.append(name)
|
||||
|
||||
grouped[name].append(announcement)
|
||||
|
||||
application_notifications = [
|
||||
{
|
||||
"application_name": app_name,
|
||||
"announcements": [
|
||||
{
|
||||
"announcement_number": announcement.announcement_number,
|
||||
"announcement_text": announcement.announcement_text,
|
||||
"announcement_url": announcement.announcement_url,
|
||||
"hide_announcement": announcement.hide_announcement,
|
||||
}
|
||||
for announcement in grouped.get(app_name, [])
|
||||
],
|
||||
}
|
||||
for app_name in ordered_names
|
||||
]
|
||||
|
||||
response = {
|
||||
"notifications": application_notifications,
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
@@ -228,8 +228,23 @@ class TestCurrentNotifications(NoSocketsTestCase):
|
||||
# The implementation calls .objects.annotate(...).order_by(...)
|
||||
mock_model.objects.annotate.return_value.order_by.return_value = mock_list
|
||||
result = admin_status._current_notifications()
|
||||
|
||||
expected_notifications = [
|
||||
{
|
||||
"application_name": mock_ann.application_name,
|
||||
"announcements": [
|
||||
{
|
||||
"announcement_number": mock_ann.announcement_number,
|
||||
"announcement_text": mock_ann.announcement_text,
|
||||
"announcement_url": mock_ann.announcement_url,
|
||||
"hide_announcement": mock_ann.hide_announcement,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
self.assertIn("notifications", result)
|
||||
self.assertEqual(result["notifications"], mock_list)
|
||||
self.assertEqual(result["notifications"], expected_notifications)
|
||||
|
||||
def test_returns_empty_notifications_when_none_saved(self):
|
||||
with patch(MODULE_PATH + ".ApplicationAnnouncement") as mock_model:
|
||||
@@ -238,6 +253,35 @@ class TestCurrentNotifications(NoSocketsTestCase):
|
||||
self.assertIn("notifications", result)
|
||||
self.assertEqual(list(result["notifications"]), [])
|
||||
|
||||
def test_if_name_not_in_grouped_is_false_on_second_iteration(self):
|
||||
ann1 = MagicMock()
|
||||
ann1.application_name = "Same App"
|
||||
ann1.announcement_number = 5
|
||||
ann1.announcement_text = "First"
|
||||
|
||||
ann2 = MagicMock()
|
||||
ann2.application_name = "Same App"
|
||||
ann2.announcement_number = 4
|
||||
ann2.announcement_text = "Second"
|
||||
|
||||
with patch(MODULE_PATH + ".ApplicationAnnouncement") as mock_model:
|
||||
mock_model.objects.annotate.return_value.order_by.return_value = [
|
||||
ann1,
|
||||
ann2,
|
||||
]
|
||||
result = admin_status._current_notifications()
|
||||
self.assertIn("notifications", result)
|
||||
notifications = result["notifications"]
|
||||
# the grouped logic should produce a single application entry (if condition false on second)
|
||||
self.assertEqual(len(notifications), 1)
|
||||
self.assertEqual(notifications[0]["application_name"], "Same App")
|
||||
# and both announcements should be present under that single application
|
||||
self.assertEqual(len(notifications[0]["announcements"]), 2)
|
||||
self.assertEqual(
|
||||
[a["announcement_number"] for a in notifications[0]["announcements"]],
|
||||
[5, 4],
|
||||
)
|
||||
|
||||
|
||||
class TestFetchTagsFromGitlab(NoSocketsTestCase):
|
||||
@requests_mock.mock()
|
||||
|
||||
Reference in new issue
Block a user