feat: add announcements
This commit is contained in:
		@@ -5,7 +5,7 @@ from django.utils.html import format_html
 | 
				
			|||||||
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice
 | 
					from .models import User, Language, Text, ReportComment, ReportAdoptionNotice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
 | 
					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
 | 
					# Define an inline admin descriptor for Employee model
 | 
				
			||||||
@@ -62,3 +62,4 @@ admin.site.register(Image)
 | 
				
			|||||||
admin.site.register(ModerationAction)
 | 
					admin.site.register(ModerationAction)
 | 
				
			||||||
admin.site.register(Language)
 | 
					admin.site.register(Language)
 | 
				
			||||||
admin.site.register(Text)
 | 
					admin.site.register(Text)
 | 
				
			||||||
 | 
					admin.site.register(Announcement)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								src/fellchensammlung/migrations/0005_announcement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/fellchensammlung/migrations/0005_announcement.py
									
									
									
									
									
										Normal file
									
								
							@@ -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",),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/fellchensammlung/migrations/0006_announcement_type.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/fellchensammlung/migrations/0006_announcement_type.py
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -4,6 +4,7 @@ from django.db import models
 | 
				
			|||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from django.utils import timezone
 | 
				
			||||||
from django.dispatch import receiver
 | 
					from django.dispatch import receiver
 | 
				
			||||||
from django.db.models.signals import post_save
 | 
					from django.db.models.signals import post_save
 | 
				
			||||||
from django.contrib.auth.models import Group
 | 
					from django.contrib.auth.models import Group
 | 
				
			||||||
@@ -371,6 +372,55 @@ class Text(models.Model):
 | 
				
			|||||||
        return f"{self.title} ({self.language})"
 | 
					        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 Comment(models.Model):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Class to store comments in markdown content
 | 
					    Class to store comments in markdown content
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -446,4 +446,33 @@ textarea {
 | 
				
			|||||||
    .btn {
 | 
					    .btn {
 | 
				
			||||||
        margin: 5px;
 | 
					        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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,6 +2,9 @@
 | 
				
			|||||||
{% load i18n %}
 | 
					{% load i18n %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    {% for announcement in announcements %}
 | 
				
			||||||
 | 
					        {% include "fellchensammlung/partials/partial-announcement.html" %}
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
    <h1>{% translate "Notfellchen - Vermittlungen finden" %}</h1>
 | 
					    <h1>{% translate "Notfellchen - Vermittlungen finden" %}</h1>
 | 
				
			||||||
    <p>{% translate "Alle Tiere brauchen ein liebendes Zuhause. Damit keins vergessen wird gibt es diese Seite. Entwickelt und betreut von " %}<em><a
 | 
					    <p>{% translate "Alle Tiere brauchen ein liebendes Zuhause. Damit keins vergessen wird gibt es diese Seite. Entwickelt und betreut von " %}<em><a
 | 
				
			||||||
            href="https://hyteck.de">moanos</a></em>!</p>
 | 
					            href="https://hyteck.de">moanos</a></em>!</p>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					{% load i18n %}
 | 
				
			||||||
 | 
					{% load custom_tags %}
 | 
				
			||||||
 | 
					<div class="announcement {{ announcement.type }}">
 | 
				
			||||||
 | 
					    <div class="announcement-header">
 | 
				
			||||||
 | 
					        <h1 class="announcement">{{ announcement.title }}</h1>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					        {{ announcement.content | render_markdown }}
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -14,13 +14,16 @@ from fellchensammlung import logger
 | 
				
			|||||||
from fellchensammlung.models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
 | 
					from fellchensammlung.models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
 | 
				
			||||||
    Member
 | 
					    Member
 | 
				
			||||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm
 | 
					from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm
 | 
				
			||||||
from .models import Language
 | 
					from .models import Language, Announcement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def index(request):
 | 
					def index(request):
 | 
				
			||||||
    """View function for home page of site."""
 | 
					    """View function for home page of site."""
 | 
				
			||||||
    latest_adoption_list = AdoptionNotice.objects.order_by("-created_at")[:5]
 | 
					    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)
 | 
					    return render(request, 'fellchensammlung/index.html', context=context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								src/tests/test_models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/tests/test_models.py
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in New Issue
	
	Block a user