Compare commits
10 Commits
4e953c83ea
...
bf54bc5d51
Author | SHA1 | Date | |
---|---|---|---|
bf54bc5d51 | |||
93ae172431 | |||
03d40a5092 | |||
993f8f9cd2 | |||
8efc0aad21 | |||
3a6e7f5344 | |||
dac9661d51 | |||
b9bfa8e359 | |||
d07589464c | |||
1880da5151 |
19
README.md
19
README.md
@ -44,15 +44,16 @@ nf query_location <query>
|
|||||||
There is a system for customizing texts in Notfellchen. Not every change of a tet should mean an update of the software. But this should also not become a CMS.
|
There is a system for customizing texts in Notfellchen. Not every change of a tet should mean an update of the software. But this should also not become a CMS.
|
||||||
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 |
|
||||||
| Any rule | About |
|
| `external_site_warning` | External Site Warning |
|
||||||
|
| Any rule | About |
|
||||||
|
|
||||||
# Developer Notes
|
# Developer Notes
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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">
|
||||||
{% blocktranslate %}
|
{% if external_site_warning %}
|
||||||
<p>Achtung du verlässt notfellchen.org</p>
|
{{ external_site_warning.content | render_markdown }}
|
||||||
{% endblocktranslate %}
|
{% else %}
|
||||||
<a href="{{ url }}" class="btn button" >{% translate "Weiter" %}</a>
|
{% blocktranslate %}
|
||||||
|
<p>Achtung du verlässt notfellchen.org</p>
|
||||||
|
{% endblocktranslate %}
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ url }}" class="btn button">{% translate "Weiter" %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -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
25
src/tests/test_forms.py
Normal 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())
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user