From e5a5fd5a10d7b91a595dd75ff271ccbb486f2bde Mon Sep 17 00:00:00 2001 From: moanos Date: Fri, 31 May 2024 09:58:03 +0200 Subject: [PATCH] feat: add announcements --- src/fellchensammlung/admin.py | 3 +- .../migrations/0005_announcement.py | 41 +++++++++++ .../migrations/0006_announcement_type.py | 26 +++++++ src/fellchensammlung/models.py | 50 +++++++++++++ .../static/fellchensammlung/css/styles.css | 29 ++++++++ .../templates/fellchensammlung/index.html | 3 + .../partials/partial-announcement.html | 10 +++ src/fellchensammlung/views.py | 7 +- src/tests/test_models.py | 72 +++++++++++++++++++ 9 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 src/fellchensammlung/migrations/0005_announcement.py create mode 100644 src/fellchensammlung/migrations/0006_announcement_type.py create mode 100644 src/fellchensammlung/templates/fellchensammlung/partials/partial-announcement.html create mode 100644 src/tests/test_models.py diff --git a/src/fellchensammlung/admin.py b/src/fellchensammlung/admin.py index 97e9cd6..cf4af59 100644 --- a/src/fellchensammlung/admin.py +++ b/src/fellchensammlung/admin.py @@ -5,7 +5,7 @@ from django.utils.html import format_html from .models import User, Language, Text, ReportComment, ReportAdoptionNotice from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \ - Member, Comment, Report + Member, Comment, Report, Announcement # Define an inline admin descriptor for Employee model @@ -62,3 +62,4 @@ admin.site.register(Image) admin.site.register(ModerationAction) admin.site.register(Language) admin.site.register(Text) +admin.site.register(Announcement) diff --git a/src/fellchensammlung/migrations/0005_announcement.py b/src/fellchensammlung/migrations/0005_announcement.py new file mode 100644 index 0000000..55282da --- /dev/null +++ b/src/fellchensammlung/migrations/0005_announcement.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.6 on 2024-06-05 04:21 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("fellchensammlung", "0004_alter_report_reported_broken_rules"), + ] + + operations = [ + migrations.CreateModel( + name="Announcement", + fields=[ + ( + "text_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="fellchensammlung.text", + ), + ), + ("logged_in_only", models.BooleanField(default=False)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "publish_start_time", + models.DateTimeField(verbose_name="Veröffentlichungszeitpunk"), + ), + ( + "publish_end_time", + models.DateTimeField(verbose_name="Veröffentlichungsende"), + ), + ], + bases=("fellchensammlung.text",), + ), + ] diff --git a/src/fellchensammlung/migrations/0006_announcement_type.py b/src/fellchensammlung/migrations/0006_announcement_type.py new file mode 100644 index 0000000..3c08a15 --- /dev/null +++ b/src/fellchensammlung/migrations/0006_announcement_type.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.6 on 2024-06-05 06:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("fellchensammlung", "0005_announcement"), + ] + + operations = [ + migrations.AddField( + model_name="announcement", + name="type", + field=models.CharField( + choices=[ + ("important", "important"), + ("warning", "warning"), + ("info", "info"), + ], + default="info", + max_length=100, + ), + ), + ] diff --git a/src/fellchensammlung/models.py b/src/fellchensammlung/models.py index 3b9de56..71748d4 100644 --- a/src/fellchensammlung/models.py +++ b/src/fellchensammlung/models.py @@ -4,6 +4,7 @@ from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ from datetime import datetime +from django.utils import timezone from django.dispatch import receiver from django.db.models.signals import post_save from django.contrib.auth.models import Group @@ -371,6 +372,55 @@ class Text(models.Model): return f"{self.title} ({self.language})" +class Announcement(Text): + """ + Class to store announcements that should be displayed for all users + """ + logged_in_only = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + publish_start_time = models.DateTimeField(verbose_name="Veröffentlichungszeitpunk") + publish_end_time = models.DateTimeField(verbose_name="Veröffentlichungsende") + IMPORTANT = "important" + WARNING = "warning" + INFO = "info" + TYPES = { + IMPORTANT: "important", + WARNING: "warning", + INFO: "info", + } + type = models.CharField(choices=TYPES, max_length=100, default=INFO) + + @property + def is_active(self): + return self.publish_start_time < timezone.now() < self.publish_end_time + + def __str__(self): + return f"[{'🟢' if self.is_active else '🔴'}]{self.title} ({self.language})" + + @staticmethod + def get_active_announcements(logged_in=False, language=None): + if logged_in: + all_active_announcements = [a for a in Announcement.objects.all() if a.is_active] + else: + all_active_announcements = [a for a in Announcement.objects.filter(logged_in_only=False) if a.is_active] + if language is None: + return all_active_announcements + else: + if logged_in: + announcements_in_language = Announcement.objects.filter(language=language) + else: + announcements_in_language = Announcement.objects.filter(language=language, logged_in_only=False) + active_announcements_in_language = [a for a in announcements_in_language if a.is_active] + + untranslated_announcements = [] + text_codes = [announcement.text_code for announcement in active_announcements_in_language] + for announcement in all_active_announcements: + if announcement.language != language and announcement.text_code not in text_codes: + untranslated_announcements.append(announcement) + return active_announcements_in_language + untranslated_announcements + + + class Comment(models.Model): """ Class to store comments in markdown content diff --git a/src/fellchensammlung/static/fellchensammlung/css/styles.css b/src/fellchensammlung/static/fellchensammlung/css/styles.css index 4f4581a..bbeec35 100644 --- a/src/fellchensammlung/static/fellchensammlung/css/styles.css +++ b/src/fellchensammlung/static/fellchensammlung/css/styles.css @@ -446,4 +446,33 @@ textarea { .btn { margin: 5px; } +} + +.announcement { + flex: 1 100%; + margin: 10px; + border-radius: 8px; + padding: 5px; + background: var(--background-three); + color: var(--text-two); + + h1 { + font-size: 1.2rem; + margin: 0px; + padding: 0px; + color: var(--text-two); + text-shadow: none; + } +} + +.important { + border: #e01137 4px solid; +} + +.warning { + border: #e09e11 4px solid; +} + +.info { + border: rgba(17, 58, 224, 0.51) 4px solid; } \ No newline at end of file diff --git a/src/fellchensammlung/templates/fellchensammlung/index.html b/src/fellchensammlung/templates/fellchensammlung/index.html index 35e942f..4ee1ac0 100644 --- a/src/fellchensammlung/templates/fellchensammlung/index.html +++ b/src/fellchensammlung/templates/fellchensammlung/index.html @@ -2,6 +2,9 @@ {% load i18n %} {% block content %} + {% for announcement in announcements %} + {% include "fellchensammlung/partials/partial-announcement.html" %} + {% endfor %}

{% translate "Notfellchen - Vermittlungen finden" %}

{% translate "Alle Tiere brauchen ein liebendes Zuhause. Damit keins vergessen wird gibt es diese Seite. Entwickelt und betreut von " %}moanos!

diff --git a/src/fellchensammlung/templates/fellchensammlung/partials/partial-announcement.html b/src/fellchensammlung/templates/fellchensammlung/partials/partial-announcement.html new file mode 100644 index 0000000..ef174f5 --- /dev/null +++ b/src/fellchensammlung/templates/fellchensammlung/partials/partial-announcement.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% load custom_tags %} +
+
+

{{ announcement.title }}

+
+

+ {{ announcement.content | render_markdown }} +

+
diff --git a/src/fellchensammlung/views.py b/src/fellchensammlung/views.py index 6f16886..77151f6 100644 --- a/src/fellchensammlung/views.py +++ b/src/fellchensammlung/views.py @@ -14,13 +14,16 @@ from fellchensammlung import logger from fellchensammlung.models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \ Member from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm -from .models import Language +from .models import Language, Announcement def index(request): """View function for home page of site.""" latest_adoption_list = AdoptionNotice.objects.order_by("-created_at")[:5] - context = {"adoption_notices": latest_adoption_list} + language_code = translation.get_language() + lang = Language.objects.get(languagecode=language_code) + active_announcements = Announcement.get_active_announcements(lang) + context = {"adoption_notices": latest_adoption_list, "announcements": active_announcements} return render(request, 'fellchensammlung/index.html', context=context) diff --git a/src/tests/test_models.py b/src/tests/test_models.py new file mode 100644 index 0000000..fc0afb8 --- /dev/null +++ b/src/tests/test_models.py @@ -0,0 +1,72 @@ +from datetime import datetime, timedelta +from django.utils import timezone + +from django.test import TestCase +from model_bakery import baker + +from fellchensammlung.models import Announcement, Language + + +class AnnouncementTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.language_de = baker.make(Language, name="Deutsch_", languagecode="de") + cls.language_en = baker.make(Language, name="English_", languagecode="en") + cls.announcement1 = baker.make(Announcement, title="Notfellchen reduziert um 1000%", + content="Jetzt adoptieren was da ist!", + publish_start_time=timezone.now() + timedelta(hours=-1), + publish_end_time=timezone.now() + timedelta(hours=1), + text_code="advert1", + language=cls.language_de) + cls.announcement2 = baker.make(Announcement, title="Notfellchen now on sale!", + content="Adopt now!", + publish_start_time=timezone.now() + timedelta(hours=-1), + publish_end_time=timezone.now() + timedelta(hours=1), + text_code="advert1", + language=cls.language_en) + + cls.announcement3 = baker.make(Announcement, title="We got hacked", + content="Hackers threaten to release incredibly sweet animal photos!", + publish_start_time=timezone.now() + timedelta(hours=-1), + publish_end_time=timezone.now() + timedelta(hours=1), + text_code="hacked", + language=cls.language_en) + + cls.announcement4 = baker.make(Announcement, title="New function: Nothing", + content="You can now also do NOTHING on this side! NOTHING will help you to be " + "more productive", + publish_start_time=timezone.now() + timedelta(hours=1), + publish_end_time=datetime.now() + timedelta(hours=2), + text_code="inactive", + language=cls.language_en) + + cls.announcement5 = baker.make(Announcement, title="Secret for all logged in", + content="You can create adoption notices yourself", + publish_start_time=timezone.now() + timedelta(hours=-1), + publish_end_time=datetime.now() + timedelta(hours=2), + text_code="secret", + language=cls.language_en, + logged_in_only=True) + + def test_active_announcements(self): + active_announcements = Announcement.get_active_announcements() + self.assertTrue(self.announcement1 in active_announcements) + self.assertTrue(self.announcement2 in active_announcements) + self.assertTrue(self.announcement3 in active_announcements) + self.assertTrue(self.announcement4 not in active_announcements) + self.assertTrue(self.announcement5 not in active_announcements) + + active_announcements = Announcement.get_active_announcements(language=self.language_de) + self.assertTrue(self.announcement1 in active_announcements) + self.assertTrue(self.announcement3 in active_announcements) + self.assertTrue(self.announcement2 not in active_announcements) + self.assertTrue(self.announcement4 not in active_announcements) + self.assertTrue(self.announcement5 not in active_announcements) + + active_announcements = Announcement.get_active_announcements(language=self.language_de, logged_in=True) + self.assertTrue(self.announcement1 in active_announcements) + self.assertTrue(self.announcement3 in active_announcements) + self.assertTrue(self.announcement2 not in active_announcements) + self.assertTrue(self.announcement4 not in active_announcements) + self.assertTrue(self.announcement5 in active_announcements) +