Compare commits

..

10 Commits

Author SHA1 Message Date
bf54bc5d51 test: fix test by setting trust level of admin user as admin
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-11-12 22:44:00 +01:00
93ae172431 test: fix name of AN 2024-11-12 22:39:07 +01:00
03d40a5092 test: Add test for creating a standard user 2024-11-12 22:38:31 +01:00
993f8f9cd2 feat: Allow export of users as CSV 2024-11-12 17:20:07 +01:00
8efc0aad21 feat: Show ANs in admin view of user 2024-11-12 17:19:30 +01:00
3a6e7f5344 feat: Add customizable external site warning to 2024-11-12 17:18:20 +01:00
dac9661d51 feat: Add comments to admin 2024-11-12 13:17:53 +01:00
b9bfa8e359 feat: Add mail to admins when new user registers 2024-11-12 13:12:45 +01:00
d07589464c test: Add basic form test 2024-11-11 13:01:08 +01:00
1880da5151 refactor: blank line 2024-11-11 13:00:57 +01:00
9 changed files with 147 additions and 19 deletions

View File

@ -45,13 +45,14 @@ There is a system for customizing texts in Notfellchen. Not every change of a te
Therefore, a solution is used where a number of predefined texts per site are supported. These markdown texts will then be included in the site, if defined. Therefore, a solution is used where a number of predefined texts per site are supported. These markdown texts will then be included in the site, if defined.
| Textcode | Location | | Textcode | Location |
|---------------------|----------| |-------------------------|-----------------------|
| `how_to` | Index | | `how_to` | Index |
| `introduction` | Index | | `introduction` | Index |
| `privacy_statement` | About | | `privacy_statement` | About |
| `terms_of_service` | About | | `terms_of_service` | About |
| `imprint` | About | | `imprint` | About |
| `about_us` | About | | `about_us` | About |
| `external_site_warning` | External Site Warning |
| Any rule | About | | Any rule | About |
# Developer Notes # Developer Notes

View File

@ -1,11 +1,16 @@
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin import csv
from django.contrib import admin from django.contrib import admin
from django.http import HttpResponse
from django.utils.html import format_html from django.utils.html import format_html
from django.urls import reverse
from django.utils.http import urlencode
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \ from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions
from django.utils.translation import gettext_lazy as _
class StatusInline(admin.StackedInline): class StatusInline(admin.StackedInline):
@ -15,14 +20,44 @@ class StatusInline(admin.StackedInline):
@admin.register(AdoptionNotice) @admin.register(AdoptionNotice)
class AdoptionNoticeAdmin(admin.ModelAdmin): class AdoptionNoticeAdmin(admin.ModelAdmin):
search_fields = ("name__icontains", "description__icontains") search_fields = ("name__icontains", "description__icontains")
list_filter = ("owner",)
inlines = [ inlines = [
StatusInline, StatusInline,
] ]
# Re-register UserAdmin # Re-register UserAdmin
admin.site.register(User) @admin.register(User)
class UserAdmin(admin.ModelAdmin):
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
list_filter = ("is_active", "trust_level",)
actions = ("export_as_csv",)
def view_adoption_notices(self, obj):
count = obj.adoption_notices.count()
url = (
reverse("admin:fellchensammlung_adoptionnotice_changelist")
+ "?"
+ urlencode({"owner__id": f"{obj.id}"})
)
return format_html('<a href="{}">{} Adoption Notices</a>', url, count)
def export_as_csv(self, request, queryset):
meta = self.model._meta
field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
row = writer.writerow([getattr(obj, field) for field in field_names])
return response
export_as_csv.short_description = _("Ausgewählte User exportieren")
def _reported_content_link(obj): def _reported_content_link(obj):
reported_content = obj.reported_content reported_content = obj.reported_content
@ -62,6 +97,9 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
class TextAdmin(admin.ModelAdmin): class TextAdmin(admin.ModelAdmin):
search_fields = ("title__icontains", "text_code__icontains",) search_fields = ("title__icontains", "text_code__icontains",)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_filter = ("user",)
admin.site.register(Animal) admin.site.register(Animal)
admin.site.register(Species) admin.site.register(Species)

View File

@ -1,4 +1,8 @@
from venv import create
import django.conf.global_settings import django.conf.global_settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext from django.utils.translation import gettext
@ -10,6 +14,7 @@ from notfellchen.settings import host
NEWLINE = "\r\n" NEWLINE = "\r\n"
def mail_admins_new_report(report): def mail_admins_new_report(report):
subject = _("Neue Meldung") subject = _("Neue Meldung")
for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]): for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]):
@ -31,3 +36,21 @@ def mail_admins_new_report(report):
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email]) message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email])
print("Sending email to ", moderator.email) print("Sending email to ", moderator.email)
message.send() message.send()
@receiver(post_save, sender=User)
def mail_admins_new_member(sender, instance: User, created: bool, **kwargs):
if not created:
return
subject = _("Neuer User") + f": {instance.username}"
for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]):
greeting = _("Moin,") + "{NEWLINE}"
new_report_text = _("es hat sich eine neue Person registriert.") + "{NEWLINE}"
user_detail_text = _("Username") + f": {instance.username}{NEWLINE}" + _(
"E-Mail") + f": {instance.email}{NEWLINE}"
user_url = "https://" + host + instance.get_absolute_url()
link_text = f"Um alle Details zu sehen, geh bitte auf: {user_url}"
body_text = greeting + new_report_text + user_detail_text + link_text
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email])
print("Sending email to ", moderator.email)
message.send()

View File

@ -74,6 +74,10 @@ class User(AbstractUser):
def get_num_unread_notifications(self): def get_num_unread_notifications(self):
return BaseNotification.objects.filter(user=self, read=False).count() return BaseNotification.objects.filter(user=self, read=False).count()
@property
def adoption_notices(self):
return AdoptionNotice.objects.filter(owner=self)
@property @property
def owner(self): def owner(self):
return self return self

View File

@ -1,10 +1,15 @@
{% extends "fellchensammlung/base_generic.html" %} {% extends "fellchensammlung/base_generic.html" %}
{% load i18n %} {% load i18n %}
{% load custom_tags %}
{% block content %} {% block content %}
<div class="card"> <div class="card">
{% if external_site_warning %}
{{ external_site_warning.content | render_markdown }}
{% else %}
{% blocktranslate %} {% blocktranslate %}
<p>Achtung du verlässt notfellchen.org</p> <p>Achtung du verlässt notfellchen.org</p>
{% endblocktranslate %} {% endblocktranslate %}
<a href="{{ url }}" class="btn button" >{% translate "Weiter" %}</a> {% endif %}
<a href="{{ url }}" class="btn button">{% translate "Weiter" %}</a>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -537,6 +537,7 @@ def external_site_warning(request):
context = {"url": url} context = {"url": url}
language_code = translation.get_language() language_code = translation.get_language()
lang = Language.objects.get(languagecode=language_code) lang = Language.objects.get(languagecode=language_code)
Text.get_texts(["external_site_warning", "good_adoption_practices"], language=lang) texts = Text.get_texts(["external_site_warning", "good_adoption_practices"], language=lang)
context.update(texts)
return render(request, 'fellchensammlung/external_site_warning.html', context=context) return render(request, 'fellchensammlung/external_site_warning.html', context=context)

25
src/tests/test_forms.py Normal file
View File

@ -0,0 +1,25 @@
from django.test import TestCase
from fellchensammlung.forms import AdoptionNoticeFormWithDateWidgetAutoAnimal
from fellchensammlung.models import Species
from model_bakery import baker
class TestAdoptionNoticeFormWithDateWidgetAutoAnimal(TestCase):
@classmethod
def setUpTestData(cls):
rat = baker.make(Species, name="Farbratte")
def test_forms(self):
form_data = {"name": "TestAdoption3",
"species": Species.objects.first(),
"num_animals": "2",
"date_of_birth": "2024-11-04",
"sex": "M",
"group_only": "on",
"searching_since": "2024-11-10",
"location_string": "Mannheim",
"description": "Blaaaa",
"further_information": "https://notfellchen.org",
"save-and-add-another-animal": "Speichern"}
form = AdoptionNoticeFormWithDateWidgetAutoAnimal(data=form_data)
self.assertTrue(form.is_valid())

View File

@ -4,7 +4,15 @@ from django.utils import timezone
from django.test import TestCase from django.test import TestCase
from model_bakery import baker from model_bakery import baker
from fellchensammlung.models import Announcement, Language from fellchensammlung.models import Announcement, Language, User
class UserTest(TestCase):
def test_creating_user(self):
test_user_1 = User.objects.create(username="Testuser1", password="SUPERSECRET", email="test@example.org")
self.assertTrue(test_user_1.trust_level == 1)
self.assertTrue(test_user_1.trust_level == User.TRUST_LEVEL[User.MEMBER])
class AnnouncementTest(TestCase): class AnnouncementTest(TestCase):
@ -69,4 +77,3 @@ class AnnouncementTest(TestCase):
self.assertTrue(self.announcement2 not in active_announcements) self.assertTrue(self.announcement2 not in active_announcements)
self.assertTrue(self.announcement4 not in active_announcements) self.assertTrue(self.announcement4 not in active_announcements)
self.assertTrue(self.announcement5 in active_announcements) self.assertTrue(self.announcement5 in active_announcements)

View File

@ -20,7 +20,8 @@ class AnimalAndAdoptionTest(TestCase):
first_name="Max", first_name="Max",
last_name="Müller", last_name="Müller",
password='12345') password='12345')
test_user1.save() test_user0.trust_level = User.TRUST_LEVEL[User.ADMIN]
test_user0.save()
adoption1 = baker.make(AdoptionNotice, name="TestAdoption1") adoption1 = baker.make(AdoptionNotice, name="TestAdoption1")
rat = baker.make(Species, name="Farbratte") rat = baker.make(Species, name="Farbratte")
@ -50,6 +51,28 @@ class AnimalAndAdoptionTest(TestCase):
self.assertContains(response, "TestAdoption1") self.assertContains(response, "TestAdoption1")
self.assertContains(response, "Rat1") self.assertContains(response, "Rat1")
def test_creating_AN_as_admin(self):
self.client.login(username='testuser0', password='12345')
form_data = {"name": "TestAdoption4",
"species": Species.objects.first().pk,
"num_animals": "2",
"date_of_birth": "2024-11-04",
"sex": "M",
"group_only": "on",
"searching_since": "2024-11-10",
"location_string": "Mannheim",
"description": "Blaaaa",
"further_information": "https://notfellchen.org",
"save-and-add-another-animal": "Speichern"}
response = self.client.post(reverse('add-adoption'), data=form_data)
print(response.content)
self.assertTrue(response.status_code < 400)
self.assertTrue(AdoptionNotice.objects.get(name="TestAdoption4").is_active)
class SearchTest(TestCase): class SearchTest(TestCase):
@classmethod @classmethod
@ -169,3 +192,4 @@ class UpdateQueueTest(TestCase):
self.assertNotContains(response, "TestAdoption3") self.assertNotContains(response, "TestAdoption3")
self.assertFalse(self.adoption3.is_active) self.assertFalse(self.adoption3.is_active)
self.assertEqual(self.adoption3.adoptionnoticestatus.major_status, AdoptionNoticeStatus.CLOSED) self.assertEqual(self.adoption3.adoptionnoticestatus.major_status, AdoptionNoticeStatus.CLOSED)