Compare commits
44 Commits
20cbb0397a
...
2589f1c703
Author | SHA1 | Date | |
---|---|---|---|
2589f1c703 | |||
0edb9094c4 | |||
bc8feba701 | |||
f37d74a7d1 | |||
fa8612ad1a | |||
1d8a054b06 | |||
5898fbf86d | |||
cd1cdd2e0b | |||
c0f920544b | |||
36c90531a8 | |||
7f7c5a3b04 | |||
c084e56ad8 | |||
84acc3c76e | |||
e1f0014898 | |||
05b3a470f3 | |||
ebe060646a | |||
bb412be8d3 | |||
e3c48eac24 | |||
da89cdceda | |||
5a6c2c99e5 | |||
9f53836ce8 | |||
5d53d1a1dc | |||
e00dda1dc2 | |||
a93e0c819f | |||
c87733b37a | |||
9aa964bf05 | |||
dcb1d3ec15 | |||
5d9b8f3213 | |||
d12989d195 | |||
a9f384b50e | |||
afedf2d0bd | |||
a4b8486bd4 | |||
d8bcb8ece6 | |||
b01ac219a3 | |||
42320866c4 | |||
e2e6c14d57 | |||
4761c38cd2 | |||
e2bef3efe2 | |||
bbfd4c3800 | |||
b671d8fbb4 | |||
1ea04e98e8 | |||
c1a7d6790b | |||
f519f78922 | |||
551b5ed6be |
@@ -178,8 +178,13 @@ def create_location(tierheim, instance, headers):
|
||||
location_result = requests.post(f"{instance}/api/locations/", json=location_data, headers=headers)
|
||||
|
||||
if location_result.status_code != 201:
|
||||
print(
|
||||
f"Location for {tierheim["properties"]["name"]}:{location_result.status_code} {location_result.json()} not created")
|
||||
try:
|
||||
print(
|
||||
f"Location for {tierheim["properties"]["name"]}:{location_result.status_code} {location_result.json()} not created")
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
print(f"Location for {tierheim["properties"]["name"]} could not be created")
|
||||
exit()
|
||||
|
||||
return location_result.json()
|
||||
|
||||
|
||||
|
@@ -8,7 +8,7 @@ from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
|
||||
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||
SpeciesSpecificURL, ImportantLocation, SpeciesSpecialization
|
||||
SpeciesSpecificURL, ImportantLocation, SocialMediaPost
|
||||
|
||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
||||
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, Notification
|
||||
@@ -100,11 +100,6 @@ class SpeciesSpecificURLInline(admin.StackedInline):
|
||||
model = SpeciesSpecificURL
|
||||
|
||||
|
||||
class SpeciesSpecializationInline(admin.StackedInline):
|
||||
model = SpeciesSpecialization
|
||||
extra = 0
|
||||
|
||||
|
||||
@admin.register(RescueOrganization)
|
||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||
search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
|
||||
@@ -112,7 +107,6 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
|
||||
|
||||
inlines = [
|
||||
SpeciesSpecializationInline,
|
||||
SpeciesSpecificURLInline,
|
||||
]
|
||||
|
||||
@@ -168,6 +162,9 @@ class LocationAdmin(admin.ModelAdmin):
|
||||
ImportantLocationInline,
|
||||
]
|
||||
|
||||
@admin.register(SocialMediaPost)
|
||||
class SocialMediaPostAdmin(admin.ModelAdmin):
|
||||
list_filter = ("platform",)
|
||||
|
||||
admin.site.register(Animal)
|
||||
admin.site.register(Species)
|
||||
|
@@ -360,9 +360,9 @@ class LocationApiView(APIView):
|
||||
|
||||
# Log the action
|
||||
Log.objects.create(
|
||||
user=request.user_to_notify,
|
||||
user=request.user,
|
||||
action="add_location",
|
||||
text=f"{request.user_to_notify} added adoption notice {location.pk} via API",
|
||||
text=f"{request.user} added adoption notice {location.pk} via API",
|
||||
)
|
||||
|
||||
# Return success response with new adoption notice details
|
||||
|
@@ -7,29 +7,27 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
|
||||
from notfellchen.settings import base_url
|
||||
|
||||
NEWLINE = "\r\n"
|
||||
from fellchensammlung.tools.model_helpers import ndm
|
||||
|
||||
|
||||
def mail_admins_new_report(report):
|
||||
def notify_mods_new_report(report, notification_type):
|
||||
"""
|
||||
Sends an e-mail to all users that should handle the report.
|
||||
"""
|
||||
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
|
||||
report_url = base_url + report.get_absolute_url()
|
||||
context = {"report_url": report_url,
|
||||
"user_comment": report.user_comment, }
|
||||
|
||||
subject = _("Neue Meldung")
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/report.html', context)
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
mail.send_mail(subject,
|
||||
plain_message,
|
||||
from_email="info@notfellchen.org",
|
||||
recipient_list=[moderator.email],
|
||||
html_message=html_message)
|
||||
if notification_type == NotificationTypeChoices.NEW_REPORT_AN:
|
||||
title = _("Vermittlung gemeldet")
|
||||
elif notification_type == NotificationTypeChoices.NEW_COMMENT:
|
||||
title = _("Kommentar gemeldet")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
notification = Notification.objects.create(
|
||||
notification_type=notification_type,
|
||||
user_to_notify=moderator,
|
||||
report=report,
|
||||
title=title,
|
||||
)
|
||||
notification.save()
|
||||
|
||||
|
||||
def send_notification_email(notification_pk):
|
||||
@@ -37,24 +35,9 @@ def send_notification_email(notification_pk):
|
||||
|
||||
subject = f"{notification.title}"
|
||||
context = {"notification": notification, }
|
||||
if notification.notification_type == NotificationTypeChoices.NEW_REPORT_COMMENT or notification.notification_type == NotificationTypeChoices.NEW_REPORT_AN:
|
||||
context["user_comment"] = notification.report.user_comment
|
||||
context["report_url"] = f"{base_url}{notification.report.get_absolute_url()}"
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/report.html', context)
|
||||
elif notification.notification_type == NotificationTypeChoices.NEW_USER:
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/new-user.html', context)
|
||||
elif notification.notification_type == NotificationTypeChoices.AN_IS_TO_BE_CHECKED:
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/an-to-be-checked.html', context)
|
||||
elif notification.notification_type == NotificationTypeChoices.AN_WAS_DEACTIVATED:
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/an-deactivated.html', context)
|
||||
elif notification.notification_type == NotificationTypeChoices.AN_FOR_SEARCH_FOUND:
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/an-for-search-found.html', context)
|
||||
elif notification.notification_type == NotificationTypeChoices.NEW_COMMENT:
|
||||
html_message = render_to_string('fellchensammlung/mail/notifications/new-comment.html', context)
|
||||
else:
|
||||
raise NotImplementedError("Unknown notification type")
|
||||
html_message = render_to_string(ndm[notification.notification_type].email_html_template, context)
|
||||
plain_message = render_to_string(ndm[notification.notification_type].email_plain_template, context)
|
||||
|
||||
plain_message = strip_tags(html_message)
|
||||
mail.send_mail(subject, plain_message, settings.DEFAULT_FROM_EMAIL,
|
||||
[notification.user_to_notify.email],
|
||||
html_message=html_message)
|
||||
|
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-13 10:54
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0054_alter_notification_comment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='ongoing_communication',
|
||||
field=models.BooleanField(default=False, help_text='Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt.', verbose_name='In aktiver Kommunikation'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notification',
|
||||
name='user_to_notify',
|
||||
field=models.ForeignKey(help_text='Useraccount der Benachrichtigt wird', on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL, verbose_name='Empfänger*in'),
|
||||
),
|
||||
]
|
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-14 05:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0055_rescueorganization_ongoing_communication_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='rescueorganization',
|
||||
options={'ordering': ['name']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rescueorganization',
|
||||
name='specializations',
|
||||
field=models.ManyToManyField(to='fellchensammlung.species'),
|
||||
),
|
||||
]
|
@@ -0,0 +1,16 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-14 05:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0056_alter_rescueorganization_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='SpeciesSpecialization',
|
||||
),
|
||||
]
|
25
src/fellchensammlung/migrations/0058_socialmediapost.py
Normal file
25
src/fellchensammlung/migrations/0058_socialmediapost.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.1 on 2025-07-19 17:48
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fellchensammlung', '0057_delete_speciesspecialization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SocialMediaPost',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
|
||||
('platform', models.CharField(choices=[('fediverse', 'Fediverse')], max_length=255, verbose_name='Social Media Platform')),
|
||||
('url', models.URLField(verbose_name='URL')),
|
||||
('adoption_notice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
|
||||
],
|
||||
),
|
||||
]
|
@@ -17,6 +17,8 @@ from .tools import misc, geo
|
||||
from notfellchen.settings import MEDIA_URL, base_url
|
||||
from .tools.geo import LocationProxy, Position
|
||||
from .tools.misc import age_as_hr_string, time_since_as_hr_string
|
||||
from .tools.model_helpers import NotificationTypeChoices
|
||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
|
||||
|
||||
|
||||
class Language(models.Model):
|
||||
@@ -105,6 +107,9 @@ class ImportantLocation(models.Model):
|
||||
slug = models.SlugField(unique=True)
|
||||
name = models.CharField(max_length=200)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('search-by-location', kwargs={'important_location_slug': self.slug})
|
||||
|
||||
|
||||
class ExternalSourceChoices(models.TextChoices):
|
||||
OSM = "OSM", _("Open Street Map")
|
||||
@@ -118,10 +123,23 @@ class AllowUseOfMaterialsChices(models.TextChoices):
|
||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
||||
|
||||
|
||||
class RescueOrganization(models.Model):
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
class Species(models.Model):
|
||||
"""Model representing a species of animal."""
|
||||
name = models.CharField(max_length=200, help_text=_('Name der Tierart'),
|
||||
verbose_name=_('Name'))
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
"""String for representing the Model object."""
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Tierart')
|
||||
verbose_name_plural = _('Tierarten')
|
||||
|
||||
|
||||
class RescueOrganization(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
trusted = models.BooleanField(default=False, verbose_name=_('Vertrauenswürdig'))
|
||||
allows_using_materials = models.CharField(max_length=200,
|
||||
@@ -149,10 +167,19 @@ class RescueOrganization(models.Model):
|
||||
exclude_from_check = models.BooleanField(default=False, verbose_name=_('Von Prüfung ausschließen'),
|
||||
help_text=_("Organisation von der manuellen Überprüfung ausschließen, "
|
||||
"z.B. weil Tiere nicht online geführt werden"))
|
||||
ongoing_communication = models.BooleanField(default=False, verbose_name=_('In aktiver Kommunikation'),
|
||||
help_text=_(
|
||||
"Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt."))
|
||||
parent_org = models.ForeignKey("RescueOrganization", on_delete=models.PROTECT, blank=True, null=True)
|
||||
# allows to specify if a rescue organization has a specialization for dedicated species
|
||||
specializations = models.ManyToManyField(Species, blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
@@ -166,6 +193,17 @@ class RescueOrganization(models.Model):
|
||||
def adoption_notices(self):
|
||||
return AdoptionNotice.objects.filter(organization=self)
|
||||
|
||||
@property
|
||||
def adoption_notices_in_hierarchy(self):
|
||||
"""
|
||||
Shows all adoption notices of this rescue organization and all child organizations.
|
||||
"""
|
||||
adoption_notices_discovered = list(self.adoption_notices)
|
||||
if self.child_organizations:
|
||||
for child in self.child_organizations:
|
||||
adoption_notices_discovered.extend(child.adoption_notices_in_hierarchy)
|
||||
return adoption_notices_discovered
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
if self.location:
|
||||
@@ -206,6 +244,10 @@ class RescueOrganization(models.Model):
|
||||
self.exclude_from_check = True
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def child_organizations(self):
|
||||
return RescueOrganization.objects.filter(parent_org=self)
|
||||
|
||||
|
||||
# Admins can perform all actions and have the highest trust associated with them
|
||||
# Moderators can make moderation decisions regarding the deletion of content
|
||||
@@ -252,14 +294,17 @@ class User(AbstractUser):
|
||||
def get_absolute_url(self):
|
||||
return reverse("user-detail", args=[str(self.pk)])
|
||||
|
||||
def get_full_url(self):
|
||||
return f"{base_url}{self.get_absolute_url()}"
|
||||
|
||||
def get_notifications_url(self):
|
||||
return self.get_absolute_url()
|
||||
|
||||
def get_unread_notifications(self):
|
||||
return Notification.objects.filter(user=self, read=False)
|
||||
return Notification.objects.filter(user_to_notify=self, read=False)
|
||||
|
||||
def get_num_unread_notifications(self):
|
||||
return Notification.objects.filter(user=self, read=False).count()
|
||||
return Notification.objects.filter(user_to_notify=self, read=False).count()
|
||||
|
||||
@property
|
||||
def adoption_notices(self):
|
||||
@@ -285,22 +330,6 @@ class Image(models.Model):
|
||||
return f'<img src="{MEDIA_URL}/{self.image}" alt="{self.alt_text}">'
|
||||
|
||||
|
||||
class Species(models.Model):
|
||||
"""Model representing a species of animal."""
|
||||
name = models.CharField(max_length=200, help_text=_('Name der Tierart'),
|
||||
verbose_name=_('Name'))
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
"""String for representing the Model object."""
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Tierart')
|
||||
verbose_name_plural = _('Tierarten')
|
||||
|
||||
|
||||
class AdoptionNotice(models.Model):
|
||||
class Meta:
|
||||
permissions = [
|
||||
@@ -346,7 +375,7 @@ class AdoptionNotice(models.Model):
|
||||
def num_per_sex(self):
|
||||
num_per_sex = dict()
|
||||
for sex in SexChoices:
|
||||
num_per_sex[sex] = self.animals.filter(sex=sex).count
|
||||
num_per_sex[sex] = self.animals.filter(sex=sex).count()
|
||||
return num_per_sex
|
||||
|
||||
@property
|
||||
@@ -455,16 +484,28 @@ class AdoptionNotice(models.Model):
|
||||
return False
|
||||
return self.adoptionnoticestatus.is_active
|
||||
|
||||
@property
|
||||
def is_disabled(self):
|
||||
if not hasattr(self, 'adoptionnoticestatus'):
|
||||
return False
|
||||
return self.adoptionnoticestatus.is_disabled
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
if not hasattr(self, 'adoptionnoticestatus'):
|
||||
return False
|
||||
return self.adoptionnoticestatus.is_closed
|
||||
|
||||
@property
|
||||
def is_disabled_unchecked(self):
|
||||
if not hasattr(self, 'adoptionnoticestatus'):
|
||||
return False
|
||||
return self.adoptionnoticestatus.is_disabled_unchecked
|
||||
|
||||
def set_closed(self):
|
||||
def set_closed(self, minor_status=None):
|
||||
self.last_checked = timezone.now()
|
||||
self.save()
|
||||
self.adoptionnoticestatus.set_closed()
|
||||
self.adoptionnoticestatus.set_closed(minor_status)
|
||||
|
||||
def set_active(self):
|
||||
self.last_checked = timezone.now()
|
||||
@@ -489,6 +530,14 @@ class AdoptionNotice(models.Model):
|
||||
text=text,
|
||||
title=notification_title)
|
||||
|
||||
def last_posted(self, platform=None):
|
||||
if platform is None:
|
||||
last_post = SocialMediaPost.objects.filter(adoption_notice=self).order_by('-created_at').first()
|
||||
else:
|
||||
last_post = SocialMediaPost.objects.filter(adoption_notice=self, platform=platform).order_by(
|
||||
'-created_at').first()
|
||||
return last_post.created_at
|
||||
|
||||
|
||||
class AdoptionNoticeStatus(models.Model):
|
||||
"""
|
||||
@@ -552,6 +601,14 @@ class AdoptionNoticeStatus(models.Model):
|
||||
def is_active(self):
|
||||
return self.major_status == self.ACTIVE
|
||||
|
||||
@property
|
||||
def is_disabled(self):
|
||||
return self.major_status == self.DISABLED
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self.major_status == self.CLOSED
|
||||
|
||||
@property
|
||||
def is_disabled_unchecked(self):
|
||||
return self.major_status == self.DISABLED and self.minor_status == "unchecked"
|
||||
@@ -569,9 +626,12 @@ class AdoptionNoticeStatus(models.Model):
|
||||
minor_status=minor_status,
|
||||
adoption_notice=an_instance)
|
||||
|
||||
def set_closed(self):
|
||||
def set_closed(self, minor_status=None):
|
||||
self.major_status = self.MAJOR_STATUS_CHOICES[self.CLOSED]
|
||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED]["other"]
|
||||
if minor_status is None:
|
||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED]["other"]
|
||||
else:
|
||||
self.minor_status = self.MINOR_STATUS_CHOICES[self.CLOSED][minor_status]
|
||||
self.save()
|
||||
|
||||
def set_unchecked(self):
|
||||
@@ -727,6 +787,9 @@ class Report(models.Model):
|
||||
"""Returns the url to access a detailed page for the report."""
|
||||
return reverse('report-detail', args=[str(self.id)])
|
||||
|
||||
def get_full_url(self):
|
||||
return f"{base_url}{self.get_absolute_url()}"
|
||||
|
||||
def get_reported_rules(self):
|
||||
return self.reported_broken_rules.all()
|
||||
|
||||
@@ -910,33 +973,24 @@ class Comment(models.Model):
|
||||
return self.adoption_notice.get_absolute_url()
|
||||
|
||||
|
||||
class NotificationTypeChoices(models.TextChoices):
|
||||
NEW_USER = "new_user", _("Useraccount wurde erstellt")
|
||||
NEW_REPORT_AN = "new_report_an", _("Vermittlung wurde gemeldet")
|
||||
NEW_REPORT_COMMENT = "new_report_comment", _("Kommentar wurde gemeldet")
|
||||
AN_IS_TO_BE_CHECKED = "an_is_to_be_checked", _("Vermittlung muss überprüft werden")
|
||||
AN_WAS_DEACTIVATED = "an_was_deactivated", _("Vermittlung wurde deaktiviert")
|
||||
AN_FOR_SEARCH_FOUND = "an_for_search_found", _("Vermittlung für Suche gefunden")
|
||||
NEW_COMMENT = "new_comment", _("Neuer Kommentar")
|
||||
|
||||
|
||||
class Notification(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
read_at = models.DateTimeField(blank=True, null=True, verbose_name=_("Gelesen am"))
|
||||
notification_type = models.CharField(max_length=200,
|
||||
choices=NotificationTypeChoices.choices,
|
||||
verbose_name=_('Benachrichtigungsgrund'))
|
||||
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
||||
text = models.TextField(verbose_name="Inhalt")
|
||||
user_to_notify = models.ForeignKey(User,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_('Nutzer*in'),
|
||||
verbose_name=_('Empfänger*in'),
|
||||
help_text=_("Useraccount der Benachrichtigt wird"),
|
||||
related_name='user')
|
||||
title = models.CharField(max_length=100, verbose_name=_("Titel"))
|
||||
text = models.TextField(verbose_name="Inhalt")
|
||||
read = models.BooleanField(default=False)
|
||||
read_at = models.DateTimeField(blank=True, null=True, verbose_name=_("Gelesen am"))
|
||||
comment = models.ForeignKey(Comment, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Antwort'))
|
||||
adoption_notice = models.ForeignKey(AdoptionNotice, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||
adoption_notice = models.ForeignKey(AdoptionNotice, blank=True, null=True, on_delete=models.CASCADE,
|
||||
verbose_name=_('Vermittlung'))
|
||||
user_related = models.ForeignKey(User,
|
||||
blank=True, null=True,
|
||||
on_delete=models.CASCADE, verbose_name=_('Verwandter Useraccount'),
|
||||
@@ -958,6 +1012,9 @@ class Notification(models.Model):
|
||||
self.read_at = timezone.now()
|
||||
self.save()
|
||||
|
||||
def get_body_part(self):
|
||||
return NotificationDisplayMapping[self.notification_type].web_partial
|
||||
|
||||
|
||||
class Subscriptions(models.Model):
|
||||
owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in'))
|
||||
@@ -1005,13 +1062,22 @@ class SpeciesSpecificURL(models.Model):
|
||||
url = models.URLField(verbose_name=_("Tierartspezifische URL"))
|
||||
|
||||
|
||||
class SpeciesSpecialization(models.Model):
|
||||
"""
|
||||
Model that allows to specify if a rescue organization has a specialization for dedicated species
|
||||
"""
|
||||
species = models.ForeignKey(Species, on_delete=models.CASCADE, verbose_name=_("Tierart"))
|
||||
rescue_organization = models.ForeignKey(RescueOrganization, on_delete=models.CASCADE,
|
||||
verbose_name=_("Tierschutzorganisation"))
|
||||
class PlatformChoices(models.TextChoices):
|
||||
FEDIVERSE = "fediverse", _("Fediverse")
|
||||
|
||||
|
||||
class SocialMediaPost(models.Model):
|
||||
created_at = models.DateField(verbose_name=_('Erstellt am'), default=timezone.now)
|
||||
platform = models.CharField(max_length=255, verbose_name=_("Social Media Platform"),
|
||||
choices=PlatformChoices.choices)
|
||||
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
|
||||
url = models.URLField(verbose_name=_("URL"))
|
||||
|
||||
@staticmethod
|
||||
def get_an_to_post():
|
||||
adoption_notices_without_post = AdoptionNotice.objects.filter(socialmediapost__isnull=True,
|
||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
||||
return adoption_notices_without_post.first()
|
||||
|
||||
def __str__(self):
|
||||
return f"{_('Spezialisierung')} {self.species}"
|
||||
return f"{self.platform} - {self.adoption_notice}"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from django.urls import reverse
|
||||
from .models import AdoptionNotice, RescueOrganization
|
||||
from .models import AdoptionNotice, RescueOrganization, ImportantLocation, Animal
|
||||
|
||||
|
||||
class StaticViewSitemap(Sitemap):
|
||||
@@ -8,7 +8,8 @@ class StaticViewSitemap(Sitemap):
|
||||
changefreq = "weekly"
|
||||
|
||||
def items(self):
|
||||
return ["index", "search", "map", "about", "rescue-organizations"]
|
||||
return ["index", "search", "map", "about", "rescue-organizations", "buying", "imprint", "terms-of-service",
|
||||
"privacy"]
|
||||
|
||||
def location(self, item):
|
||||
return reverse(item)
|
||||
@@ -25,17 +26,6 @@ class AdoptionNoticeSitemap(Sitemap):
|
||||
return obj.updated_at
|
||||
|
||||
|
||||
class AnimalSitemap(Sitemap):
|
||||
priority = 0.2
|
||||
changefreq = "daily"
|
||||
|
||||
def items(self):
|
||||
return AdoptionNotice.objects.all()
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.updated_at
|
||||
|
||||
|
||||
class RescueOrganizationSitemap(Sitemap):
|
||||
priority = 0.3
|
||||
changefreq = "weekly"
|
||||
@@ -45,3 +35,11 @@ class RescueOrganizationSitemap(Sitemap):
|
||||
|
||||
def lastmod(self, obj):
|
||||
return obj.updated_at
|
||||
|
||||
|
||||
class SearchSitemap(Sitemap):
|
||||
priority = 0.5
|
||||
chanfreq = "daily"
|
||||
|
||||
def items(self):
|
||||
return ImportantLocation.objects.all()
|
||||
|
@@ -320,3 +320,29 @@ AN Cards
|
||||
background-color: var(--bulma-success-on-scheme);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.notification-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notification-label {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
/* Make the badge float in the top right corner of the button */
|
||||
.notification-badge {
|
||||
background-color: #fa3e3e;
|
||||
border-radius: 2px;
|
||||
color: white;
|
||||
|
||||
padding: 1px 3px;
|
||||
font-size: 8px;
|
||||
|
||||
position: absolute; /* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
@@ -5,8 +5,9 @@ from django.utils import timezone
|
||||
from notfellchen.celery import app as celery_app
|
||||
from .mail import send_notification_email
|
||||
from .tools.admin import clean_locations, deactivate_unchecked_adoption_notices, deactivate_404_adoption_notices
|
||||
from .tools.fedi import post_an_to_fedi
|
||||
from .tools.misc import healthcheck_ok
|
||||
from .models import Location, AdoptionNotice, Timestamp, RescueOrganization
|
||||
from .models import Location, AdoptionNotice, Timestamp, RescueOrganization, SocialMediaPost
|
||||
from .tools.notifications import notify_of_AN_to_be_checked
|
||||
from .tools.search import notify_search_subscribers
|
||||
|
||||
@@ -38,6 +39,13 @@ def task_deactivate_unchecked():
|
||||
set_timestamp("task_deactivate_404_adoption_notices")
|
||||
|
||||
|
||||
@celery_app.task(name="social_media.post_fedi")
|
||||
def task_post_to_fedi():
|
||||
adoption_notice = SocialMediaPost.get_an_to_post()
|
||||
post_an_to_fedi(adoption_notice)
|
||||
set_timestamp("task_social_media.post_fedi")
|
||||
|
||||
|
||||
@celery_app.task(name="commit.post_an_save")
|
||||
def post_adoption_notice_save(pk):
|
||||
instance = AdoptionNotice.objects.get(pk=pk)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load custom_tags %}
|
||||
{% load admin_urls %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
@@ -25,6 +26,20 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if adoption_notice.is_closed %}
|
||||
<article class="message is-warning">
|
||||
<div class="message-header">
|
||||
<p>{% translate 'Vermittlung deaktiviert' %}</p>
|
||||
</div>
|
||||
<div class="message-body content">
|
||||
{% blocktranslate %}
|
||||
Diese Vermittlung wurde deaktiviert. Typischerweise passiert das, wenn die Tiere erfolgreich
|
||||
vermittelt wurden.
|
||||
In den Kommentaren findest du ggf. mehr Informationen.
|
||||
{% endblocktranslate %}
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
<div class="columns">
|
||||
<div class="column is-two-thirds">
|
||||
<!--- Title level (including action dropdown) -->
|
||||
@@ -49,30 +64,59 @@
|
||||
<!--- Action menu (dropdown) --->
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
{% if is_subscribed %}
|
||||
<form class="dropdown-item" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="unsubscribe">
|
||||
|
||||
<button type="submit" id="submit">
|
||||
<i class="fas fa-bell-slash fa-fw"
|
||||
aria-hidden="true"></i> {% trans 'Deabonnieren' %}
|
||||
</button>
|
||||
|
||||
</form>
|
||||
{% else %}
|
||||
<form class="dropdown-item" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="subscribe">
|
||||
|
||||
<button type="submit" id="submit">
|
||||
<i class="fas fa-bell fa-fw"
|
||||
aria-hidden="true"></i> {% trans 'Abonnieren' %}
|
||||
</button>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
<hr class="dropdown-divider">
|
||||
|
||||
{% if has_edit_permission %}
|
||||
|
||||
<a class="dropdown-item">
|
||||
<i class="fas fa-check"
|
||||
aria-hidden="true"></i> {% trans 'Als aktiv bestätigen' %}
|
||||
</a>
|
||||
<form class="dropdown-item" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="checked_active">
|
||||
<button type="submit" id="submit">
|
||||
<i class="fas fa-check fa-fw"
|
||||
aria-hidden="true"></i> {% trans 'Als aktiv bestätigen' %}
|
||||
</button>
|
||||
</form>
|
||||
<a class="dropdown-item"
|
||||
href="{% url 'adoption-notice-edit' adoption_notice_id=adoption_notice.pk %}">
|
||||
<i class="fas fa-pencil"
|
||||
<i class="fas fa-pencil fa-fw"
|
||||
aria-hidden="true"></i> {% translate 'Bearbeiten' %}
|
||||
</a>
|
||||
<a class="dropdown-item"
|
||||
href="{% url 'adoption-notice-add-photo' adoption_notice.pk %}">
|
||||
<i class="fas fa-image"
|
||||
<i class="fas fa-image fa-fw"
|
||||
aria-hidden="true"></i> {% trans 'Bilder hinzufügen' %}
|
||||
</a>
|
||||
<a class="dropdown-item"
|
||||
href="{% url 'adoption-notice-add-animal' adoption_notice.pk %}">
|
||||
<i class="fas fa-plus"
|
||||
<i class="fas fa-plus fa-fw"
|
||||
aria-hidden="true"></i> {% trans 'Tier hinzufügen' %}
|
||||
</a>
|
||||
<a class="dropdown-item">
|
||||
<i class="fas fa-circle-xmark"
|
||||
<a class="dropdown-item"
|
||||
href="{% url 'adoption-notice-close' adoption_notice_id=adoption_notice.pk %}">
|
||||
<i class="fas fa-circle-xmark fa-fw"
|
||||
aria-hidden="true"></i> {% trans 'Deaktivieren' %}
|
||||
</a>
|
||||
<hr class="dropdown-divider">
|
||||
@@ -81,6 +125,13 @@
|
||||
<i class="fas fa-flag"
|
||||
aria-hidden="true"></i> {% trans 'Melden' %}
|
||||
</a>
|
||||
{% if request.user.is_superuser %}
|
||||
<hr class="dropdown-divider">
|
||||
<a class="dropdown-item is-warning"
|
||||
href="{% url adoption_notice_meta|admin_urlname:'change' adoption_notice.pk %}">
|
||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,6 +220,13 @@
|
||||
{% translate "Keine Beschreibung angegeben" %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
<strong>
|
||||
{% translate 'Zuletzt auf Aktualität überprüft:' %}
|
||||
</strong>
|
||||
{{ adoption_notice.last_checked|time_since_hr }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -35,6 +35,29 @@
|
||||
<p>{{ org.description | render_markdown }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if org.specializations %}
|
||||
<div class="block">
|
||||
<h3 class="title is-5">{% translate 'Spezialisierung' %}</h3>
|
||||
<div class="content">
|
||||
<ul>
|
||||
{% for specialization in org.specializations.all %}
|
||||
<li>{{ specialization }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if org.parent_org %}
|
||||
<div class="block">
|
||||
<h3 class="title is-5">{% translate 'Übergeordnete Organisation' %}</h3>
|
||||
<p>
|
||||
<span>
|
||||
<i class="fa-solid fa-building fa-fw" aria-label="{% trans 'Tierschutzorganisation' %}"></i>
|
||||
<a href="{{ org.parent_org.get_absolute_url }}"> {{ org.parent_org }}</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,7 +65,9 @@
|
||||
{% include "fellchensammlung/partials/partial-rescue-organization-contact.html" %}
|
||||
</div>
|
||||
<div class="block">
|
||||
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}"><i class="fa-solid fa-tools fa-fw"></i> Admin interface</a>
|
||||
<a class="button is-warning is-fullwidth" href="{% url org_meta|admin_urlname:'change' org.pk %}">
|
||||
<i class="fa-solid fa-tools fa-fw"></i> Admin interface
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
@@ -50,11 +75,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if org.child_organizations %}
|
||||
<div class="block">
|
||||
<h2 class="title is-2">{% translate 'Unterorganisationen' %}</h2>
|
||||
{% with rescue_organizations=org.child_organizations %}
|
||||
{% include "fellchensammlung/lists/list-animal-shelters.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<h2 class="title is-2">{% translate 'Vermittlungen der Organisation' %}</h2>
|
||||
<div class="container-cards">
|
||||
{% if org.adoption_notices %}
|
||||
{% for adoption_notice in org.adoption_notices %}
|
||||
{% if org.adoption_notices_in_hierarchy %}
|
||||
{% for adoption_notice in org.adoption_notices_in_hierarchy %}
|
||||
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
@@ -0,0 +1,14 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% block title %}<title>{% translate "403 Forbidden" %}</title>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title is-1">404 Not Found</h1>
|
||||
<p>
|
||||
{% blocktranslate %}
|
||||
Diese Seite existiert nicht.
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
{% endblock %}
|
@@ -1,15 +1,28 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
{% load custom_tags %}
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
{% if external_site_warning %}
|
||||
{{ external_site_warning.content | render_markdown }}
|
||||
{% else %}
|
||||
{% blocktranslate %}
|
||||
<p>Achtung du verlässt notfellchen.org</p>
|
||||
{% endblocktranslate %}
|
||||
{% endif %}
|
||||
<a href="{{ url }}" class="button is-primary">{% translate "Weiter" %}</a>
|
||||
<div class="block">
|
||||
<div class="message is-warning">
|
||||
{% if external_site_warning %}
|
||||
<h1 class="message-header">
|
||||
{{ external_site_warning.title }}
|
||||
</h1>
|
||||
<div class="message-body">
|
||||
{{ external_site_warning.content | render_markdown }}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<h1 class="message-header">
|
||||
{% trans 'Achtung du verlässt notfellchen.org' %}
|
||||
</h1>
|
||||
<div class="message-body">
|
||||
{% trans 'Sichere Abgabebedingungen können von uns, trotz vieler Bemühungen, nicht garantiert werden. Nimm Kontakt zu einer Rattenhilfe oder dem VdRD e.V. auf, die dich beraten können.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="block">
|
||||
<a href="{{ url }}" class="button is-primary is-fullwidth">{% translate "Weiter" %}<i class="fa fa-arrow-right fa-fw" aria-hidden="true"></i> </a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@@ -34,6 +34,10 @@
|
||||
{% translate 'Das Notfellchen Projekt' %}
|
||||
</a>
|
||||
<br/>
|
||||
<a href="{% url "buying" %}">
|
||||
{% translate 'Ratten kaufen' %}
|
||||
</a>
|
||||
<br/>
|
||||
<a href="{% url "terms-of-service" %}">
|
||||
{% translate 'Nutzungsbedingungen' %}
|
||||
</a>
|
||||
|
@@ -9,7 +9,8 @@
|
||||
<h1 class="title is-4">notfellchen.org</h1>
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="{% trans 'Hauptmenü' %}" tabindex="0" aria-expanded="false" data-target="navbarBasicExample">
|
||||
<a role="button" class="navbar-burger" aria-label="{% trans 'Hauptmenü' %}" tabindex="0" aria-expanded="false"
|
||||
data-target="navbarBasicExample">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
@@ -30,7 +31,16 @@
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
{% if user.is_authenticated %}
|
||||
|
||||
<div class="navbar-item">
|
||||
<div class="notification-container">
|
||||
<a class="notification-label" href="{% url 'user-notifications' %}">
|
||||
<i class="fas fa-bell fa-fw"></i>
|
||||
</a>
|
||||
{% if request.user.get_num_unread_notifications > 0 %}
|
||||
<span class="notification-badge">{{ request.user.get_num_unread_notifications }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<a href="{% url 'user-me' %}">
|
||||
<i class="fas fa-user fa-fw"></i> {{ user }}
|
||||
|
@@ -54,6 +54,11 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
.setting-info {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.content, .header, .footer {
|
||||
padding: 20px 15px;
|
||||
|
@@ -0,0 +1,4 @@
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
---
|
||||
{% include "fellchensammlung/mail/footer.txt" %}
|
@@ -1,3 +1,12 @@
|
||||
{% load i18n %}
|
||||
<div class="footer">
|
||||
🐀 notfellchen.org | Für Menschen die Ratten aus dem Tierschutz ein liebendes Zuhause geben wollen.
|
||||
</div>
|
||||
{% if notification %}
|
||||
<div class="setting-info">
|
||||
{% trans "Du bekommst diese Nachricht basierend auf deinen Benachrichtigungseinstellungen." %}<br>
|
||||
<a href="{{ notification.user_to_notify.get_full_url }}">
|
||||
{% trans "Einstellungen ändern" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@@ -0,0 +1,7 @@
|
||||
{% load i18n %}
|
||||
🐀 notfellchen.org | Für Menschen die Ratten aus dem Tierschutz ein liebendes Zuhause geben wollen.
|
||||
|
||||
{% if notification %}
|
||||
{% trans "Du bekommst diese Nachricht basierend auf deinen Benachrichtigungseinstellungen." %}
|
||||
{% trans "Einstellungen ändern" %}: {{ notification.user_to_notify.get_full_url }}
|
||||
{% endif %}
|
@@ -0,0 +1,7 @@
|
||||
{% extends "fellchensammlung/mail/base.txt" %}
|
||||
{% load i18n %}
|
||||
{% block content %}{% blocktranslate %}Moin,
|
||||
die Vermittlung {{ notification.adoption_notice }} wurde deaktiviert.
|
||||
{% endblocktranslate %}
|
||||
|
||||
{% translate 'Vermittlung anzeigen' %}: {{ notification.adoption_notice.get_full_url }}{% endblock %}
|
@@ -0,0 +1,9 @@
|
||||
{% extends "fellchensammlung/mail/base.txt" %}
|
||||
{% load i18n %}
|
||||
{% block content %}{% blocktranslate %}Moin,
|
||||
|
||||
es wurde eine neue Vermittlung gefunden, die deinen Kriterien entspricht: {{ notification.adoption_notice }}
|
||||
|
||||
|
||||
Vermittlung anzeigen: {{ notification.adoption_notice.get_full_url }}
|
||||
{% endblocktranslate %}{% endblock %}
|
@@ -0,0 +1,8 @@
|
||||
{% extends "fellchensammlung/mail/base.txt" %}
|
||||
{% load i18n %}
|
||||
{% block content %}{% blocktranslate %}Moin,
|
||||
|
||||
die Vermittlung {{ notification.adoption_notice }} muss überprüft werden.
|
||||
|
||||
Vermittlung anzeigen: {{ notification.adoption_notice.get_full_url }}
|
||||
{% endblocktranslate %}{% endblock %}
|
@@ -0,0 +1,11 @@
|
||||
{% extends "fellchensammlung/mail/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
{% block content %}{% blocktranslate %}Moin,
|
||||
|
||||
folgender Kommentar wurde zur Vermittlung {{ notification.adoption_notice }} hinzugefügt:
|
||||
|
||||
{{ notification.comment.text }}
|
||||
|
||||
Vermittlung anzeigen: {{ notification.adoption_notice.get_full_url }}
|
||||
{% endblocktranslate %}{% endblock %}
|
@@ -14,6 +14,6 @@
|
||||
Details findest du hier
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ notification.user_related.get_absolute_url }}" class="cta-button">{% translate 'User anzeigen' %}</a>
|
||||
<a href="{{ notification.user_related.get_full_url }}" class="cta-button">{% translate 'User anzeigen' %}</a>
|
||||
</p>
|
||||
{% endblock %}
|
@@ -0,0 +1,8 @@
|
||||
{% extends "fellchensammlung/mail/base.txt" %}
|
||||
{% load i18n %}
|
||||
{% block content %}{% blocktranslate with new_user_url=notification.user_related.get_full_url %}Moin,
|
||||
|
||||
es wurde ein neuer Useraccount erstellt.
|
||||
|
||||
User anzeigen: {{ new_user_url }}
|
||||
{% endblocktranslate %}{% endblock %}
|
@@ -5,21 +5,31 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>Moin,</p>
|
||||
<p>{% translate 'Moin' %},</p>
|
||||
<p>
|
||||
es gibt eine neue Meldung. Folgende Nachricht wurde zur Meldung hinzugefügt:
|
||||
{% blocktranslate %}
|
||||
es gibt eine neue Meldung.
|
||||
{% endblocktranslate %}
|
||||
{% if notification.report.user_comment %}
|
||||
{% blocktranslate %}
|
||||
Folgende Nachricht wurde zur Meldung hinzugefügt:
|
||||
{% endblocktranslate %}
|
||||
{% endif %}
|
||||
|
||||
</p>
|
||||
{% if notification.report.user_comment %}
|
||||
<p>
|
||||
<i>
|
||||
{{ notification.report.user_comment }}
|
||||
</i>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<i>
|
||||
{{ user_comment }}
|
||||
</i>
|
||||
{% blocktranslate %}
|
||||
Bitte bearbeite die Meldung möglichst bald.
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
<p>
|
||||
|
||||
Bitte bearbeite die Meldung möglichst bald.
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ report_url }}" class="cta-button">{% translate 'Report bearbeiten' %}</a>
|
||||
<a href="{{ notification.report.get_full_url }}" class="cta-button">{% translate 'Report bearbeiten' %}</a>
|
||||
</p>
|
||||
{% endblock %}
|
@@ -0,0 +1,11 @@
|
||||
{% extends "fellchensammlung/mail/base.txt" %}
|
||||
{% load i18n %}
|
||||
{% block content %}{% blocktranslate %}Moin,
|
||||
|
||||
es gibt eine neue Meldung. Folgende Nachricht wurde zur Meldung hinzugefügt:
|
||||
|
||||
{{ user_comment }}
|
||||
|
||||
Bitte bearbeite die Meldung möglichst bald.
|
||||
|
||||
Meldung bearbeiten: {{ report_url }}{% endblocktranslate %}{% endblock %}
|
@@ -0,0 +1,26 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title is-1">{% translate 'Vermittlung deaktivieren' %}</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label" for="reason_for_closing">{% translate 'Warum schließt du die Vermittlung?' %}</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="reason_for_closing" name="reason_for_closing">
|
||||
<option value="successful_with_notfellchen">{% translate 'Vermittelt mit Hilfe von Notfellchen' %}</option>
|
||||
<option value="successful_without_notfellchen">{% translate 'Vermittelt ohne Hilfe von Notfellchen' %}</option>
|
||||
<option value="closed_for_other_adoption_notice">{% translate 'Vermittlung zugunsten einer anderen geschlossen' %}</option>
|
||||
<option value="not_open_for_adoption_anymore">{% translate 'Nicht mehr zu vermitteln (z.B. aufgrund von Krankheit)' %}</option>
|
||||
<option value="animal_died">{% translate 'Tod des Tiers' %}</option>
|
||||
<option value="other">{% translate 'Anderer Grund' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input class="button is-warning" type="submit" value="{% translate "Vermittlung deaktivieren" %}">
|
||||
</form>
|
||||
{% endblock %}
|
@@ -0,0 +1,8 @@
|
||||
### {{ adoption_notice.name }}
|
||||
|
||||
📍 {{ adoption_notice.location }}
|
||||
|
||||
|
||||
{{ adoption_notice.description }}
|
||||
|
||||
{{ adoption_notice.get_full_url}}
|
@@ -7,13 +7,52 @@
|
||||
<div class="block">
|
||||
<h1 class="title is-1">{% translate 'Moderationstools' %}</h1>
|
||||
<div class="block">
|
||||
<a class="button is-primary is-fullwidth" href="{% url 'modqueue' %}">{% translate 'Moderationswarteschlange' %}</a>
|
||||
<a class="button is-primary is-fullwidth"
|
||||
href="{% url 'modqueue' %}">{% translate 'Moderationswarteschlange' %}</a>
|
||||
</div>
|
||||
<div class="block">
|
||||
<a class="button is-primary is-fullwidth" href="{% url 'updatequeue' %}">{% translate 'Up-To-Date Check' %}</a>
|
||||
<a class="button is-primary is-fullwidth"
|
||||
href="{% url 'updatequeue' %}">{% translate 'Up-To-Date Check' %}</a>
|
||||
</div>
|
||||
<div class="block">
|
||||
<a class="button is-primary is-fullwidth" href="{% url 'organization-check' %}">{% translate 'Organisations Check' %}</a>
|
||||
<a class="button is-primary is-fullwidth"
|
||||
href="{% url 'organization-check' %}">{% translate 'Organisations Check' %}</a>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
{% if action_was_posting %}
|
||||
{% if posted_successfully %}
|
||||
<div class="message is-success">
|
||||
<div class="message-header">
|
||||
{% translate 'Vermittlung gepostet' %}
|
||||
</div>
|
||||
<div class="message-body">
|
||||
{% blocktranslate with post_url=post.url %}
|
||||
Link zum Post: <a href={{ post_url }}>{{ post_url }}</a>
|
||||
{% endblocktranslate %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="message is-danger">
|
||||
<div class="message-header">
|
||||
{% translate 'Vermittlung konnte nicht gepostet werden' %}
|
||||
</div>
|
||||
<div class="message-body">
|
||||
{{ error_message }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<form class="cell" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="post_to_fedi">
|
||||
<button class="button is-fullwidth is-warning is-primary" type="submit" id="submit">
|
||||
<i class="fa-solid fa-bullhorn fa-fw"></i> {% translate "Vermittlung ins Fediverse posten" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -0,0 +1,32 @@
|
||||
{% extends "fellchensammlung/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}<title>{% trans 'Benachrichtigungen' %}</title>{% endblock %}
|
||||
{% block content %}
|
||||
<div class="block">
|
||||
<h1 class="title is-1">{% translate 'Benachrichtigungen' %}</h1>
|
||||
{% if notifications_unread|length > 0 %}
|
||||
<div class="block">
|
||||
<form class="button is-danger" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="notification_mark_all_read">
|
||||
<button class="" type="submit" id="submit">
|
||||
{% trans 'Alle Benachrichtigungen als gelesen markieren' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="block">
|
||||
{% with notifications=notifications_unread %}
|
||||
{% include "fellchensammlung/lists/list-notifications.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<h2 class="title is-2">{% translate 'Zuletzt gelesene Benachrichtigungen' %}</h2>
|
||||
{% with notifications=notifications_read_last %}
|
||||
{% include "fellchensammlung/lists/list-notifications.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% blocktranslate with adoption_notice_title=notification.adoption_notice.name %}
|
||||
Die Vermittlung <strong>{{ adoption_notice_title }}</strong> wurde deaktiviert.
|
||||
{% endblocktranslate %}
|
||||
<a href="{{ notification.adoption_notice.get_full_url }}">{% translate 'Vermittlung anzeigen' %}</a>
|
@@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% blocktranslate %}
|
||||
Es wurde eine neue Vermittlung gefunden, die deinen Kriterien entspricht:
|
||||
{% endblocktranslate %}
|
||||
<a href="{{ notification.adoption_notice.get_full_url }}">{{ notification.adoption_notice.name }}</a>
|
@@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% blocktranslate with adoption_notice_title=notification.adoption_notice.name %}
|
||||
Die Vermittlung <strong>{{ adoption_notice_title }}</strong> muss überprüft werden.
|
||||
{% endblocktranslate %}
|
||||
<a href="{{ notification.adoption_notice.get_full_url }}">{% translate 'Vermittlung anzeigen' %}</a>
|
@@ -0,0 +1,13 @@
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
<p>
|
||||
{% blocktranslate with adoption_notice_title=notification.adoption_notice.name %}
|
||||
Folgender Kommentar wurde zur Vermittlung <strong>{{ adoption_notice_title }}</strong> hinzugefügt:
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
<p><i>
|
||||
{{ notification.comment.text | render_markdown }}
|
||||
</i></p>
|
||||
<p>
|
||||
<a href="{{ notification.adoption_notice.get_full_url }}">{% translate 'Vermittlung anzeigen' %}</a>
|
||||
</p>
|
@@ -0,0 +1,13 @@
|
||||
{% load i18n %}
|
||||
<p>
|
||||
{% blocktranslate %}
|
||||
Es gibt eine neue Meldung. Folgende Nachricht wurde zur Meldung hinzugefügt:
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
<p>
|
||||
<i>
|
||||
{{ notification.report.user_comment }}
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<a href="{{ notification.report.get_absolute_url }}">{% translate 'Meldung anzeigen' %}</a>
|
@@ -0,0 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% blocktranslate %}
|
||||
Es wurde ein neuer Useraccount erstellt:
|
||||
{% endblocktranslate %}
|
||||
<a href="{{ notification.user_related.get_full_url }}">{% translate 'User anzeigen' %}</a>
|
@@ -15,13 +15,24 @@
|
||||
|
||||
<div class="cell">
|
||||
|
||||
<p>
|
||||
<i class="fa-solid fa-location-dot fa-fw"></i>
|
||||
{% if adoption_notice.location %}
|
||||
{{ adoption_notice.location }}
|
||||
{% else %}
|
||||
{{ adoption_notice.location_string }}
|
||||
{% endif %}</p>
|
||||
{% if adoption_notice.organization %}
|
||||
<div class="cell">
|
||||
<span>
|
||||
<i class="fa-solid fa-building fa-fw" aria-label="{% trans 'Tierschutzorganisation' %}"></i>
|
||||
<a href="{{ adoption_notice.organization.get_absolute_url }}"> {{ adoption_notice.organization }}</a>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
<i class="fa-solid fa-location-dot fa-fw"></i>
|
||||
{% if adoption_notice.location %}
|
||||
{{ adoption_notice.location }}
|
||||
{% else %}
|
||||
{{ adoption_notice.location_string }}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="cell">
|
||||
{% include "fellchensammlung/partials/sex-overview.html" %}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="card-header">
|
||||
<div class="card-header-title">
|
||||
<h2 class="title is-4">
|
||||
<a href="{{ rescue_org.get_absolute_url }}">{{ rescue_org.name }}</a>
|
||||
<a href="{{ rescue_org.get_absolute_url }}" target="_blank">{{ rescue_org.name }}</a>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,17 +1,17 @@
|
||||
{% load i18n %}
|
||||
{% load custom_tags %}
|
||||
<div class="notification">
|
||||
<form class="delete" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="notification_mark_read">
|
||||
<input type="hidden" name="notification_id" value="{{ notification.pk }}">
|
||||
<button class="" type="submit" id="submit"></button>
|
||||
</form>
|
||||
<div class="notification-header">
|
||||
<a href="{{ notification.url }}" ><b>{{ notification.title }}</b></a>
|
||||
<i class="card-timestamp">{{ notification.created_at }}</i>
|
||||
<form class="notification-card-mark-read" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="notification_mark_read">
|
||||
<input type="hidden" name="notification_id" value="{{ notification.pk }}">
|
||||
<button class="btn2" type="submit" id="submit"><i class="fa-solid fa-check"></i></button>
|
||||
</form>
|
||||
<a href="{{ notification.url }}"><b>{{ notification.title }}</b></a>
|
||||
<i class="card-timestamp">{{ notification.created_at|time_since_hr }}</i>
|
||||
</div>
|
||||
<div class="notification-body">
|
||||
{% include notification.get_body_part %}
|
||||
</div>
|
||||
<p>
|
||||
{{ notification.text | render_markdown }}
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<p>
|
||||
<b><i class="fa-solid fa-location-dot"></i></b>
|
||||
{% if rescue_organization.location %}
|
||||
{{ rescue_organization.location.str }}
|
||||
{{ rescue_organization.location }}
|
||||
{% else %}
|
||||
{{ rescue_organization.location_string }}
|
||||
{% endif %}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<h1 class="title is-1">{% translate "Aktualitätscheck" %}</h1>
|
||||
<p class="subtitle is-2">{% translate "Überprüfe ob es in Tierheimen neue Tiere gibt die ein Zuhause suchen" %}</p>
|
||||
<p class="subtitle is-3">{% translate "Überprüfe ob es in Tierheimen neue Tiere gibt die ein Zuhause suchen" %}</p>
|
||||
|
||||
<div class="block">
|
||||
<div class="columns">
|
||||
@@ -15,6 +15,13 @@
|
||||
<div class="column">
|
||||
<strong>Geprüft sind {{ percentage_checked|stringformat:"0.2f" }}%</strong>
|
||||
</div>
|
||||
<div class="column">
|
||||
{% if dq %}
|
||||
<a class="button is-info" href="{% url 'organization-check' %}">{% translate 'Datenergänzung deaktivieren' %}</a>
|
||||
{% else %}
|
||||
<a class="button is-info is-light" href="{% url 'organization-check-dq' %}">{% translate 'Datenergänzung aktivieren' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +37,18 @@
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="block">
|
||||
<h2 class="title is-3">{% translate "In aktiver Kommunikation" %}</h2>
|
||||
<div class="grid is-col-min-15">
|
||||
{% for rescue_org in rescue_orgs_with_ongoing_communication %}
|
||||
<div class="cell">
|
||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="block">
|
||||
<h2 class="title is-3">{% translate "Zuletzt geprüft" %}</h2>
|
||||
<div class="grid is-col-min-15">
|
||||
|
@@ -4,7 +4,9 @@ from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from django.utils.safestring import mark_safe
|
||||
from urllib.parse import urlparse
|
||||
from django.utils import timezone
|
||||
|
||||
from fellchensammlung.tools.misc import time_since_as_hr_string
|
||||
from notfellchen import settings
|
||||
from fellchensammlung.models import TrustLevel
|
||||
|
||||
@@ -114,3 +116,9 @@ def dictkey(d, key):
|
||||
def host():
|
||||
# Will not work for localhost or deployments without https
|
||||
return f"https://{settings.host}"
|
||||
|
||||
|
||||
@register.filter
|
||||
def time_since_hr(timestamp):
|
||||
t_delta = timezone.now() - timestamp
|
||||
return time_since_as_hr_string(t_delta)
|
||||
|
97
src/fellchensammlung/tools/fedi.py
Normal file
97
src/fellchensammlung/tools/fedi.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
import requests
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from fellchensammlung.models import SocialMediaPost, PlatformChoices
|
||||
from notfellchen import settings
|
||||
|
||||
|
||||
class FediClient:
|
||||
def __init__(self, access_token, api_base_url):
|
||||
"""
|
||||
:param access_token: Your server API access token.
|
||||
:param api_base_url: The base URL of the Fediverse instance (e.g., 'https://gay-pirate-assassins.de').
|
||||
"""
|
||||
self.access_token = access_token
|
||||
self.api_base_url = api_base_url.rstrip('/')
|
||||
self.headers = {
|
||||
'Authorization': f'Bearer {self.access_token}',
|
||||
}
|
||||
|
||||
def upload_media(self, image_path, alt_text):
|
||||
"""
|
||||
Uploads media (image) to the server and returns the media ID.
|
||||
:param image_path: Path to the image file to upload.
|
||||
:param alt_text: Description (alt text) for the image.
|
||||
:return: The media ID of the uploaded image.
|
||||
"""
|
||||
|
||||
media_endpoint = f'{self.api_base_url}/api/v2/media'
|
||||
|
||||
with open(image_path, 'rb') as image_file:
|
||||
files = {
|
||||
'file': image_file,
|
||||
'description': (None, alt_text)
|
||||
}
|
||||
response = requests.post(media_endpoint, headers=self.headers, files=files)
|
||||
|
||||
# Raise exception if upload fails
|
||||
response.raise_for_status()
|
||||
|
||||
# Parse and return the media ID from the response
|
||||
media_id = response.json().get('id')
|
||||
return media_id
|
||||
|
||||
def post_status(self, status, media_ids=None):
|
||||
"""
|
||||
Posts a status to Mastodon with optional media.
|
||||
:param status: The text of the status to post.
|
||||
:param media_ids: A list of media IDs to attach to the status (optional).
|
||||
:return: The response from the Mastodon API.
|
||||
"""
|
||||
status_endpoint = f'{self.api_base_url}/api/v1/statuses'
|
||||
|
||||
payload = {
|
||||
'status': status,
|
||||
'media_ids[]': media_ids if media_ids else []
|
||||
}
|
||||
response = requests.post(status_endpoint, headers=self.headers, data=payload)
|
||||
|
||||
# Raise exception if posting fails
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json()
|
||||
|
||||
def post_status_with_images(self, status, images):
|
||||
"""
|
||||
Uploads one or more image, then posts a status with that images and alt text.
|
||||
:param status: The text of the status.
|
||||
:param image_paths: The paths to the image file.
|
||||
:param alt_text: The alt text for the image.
|
||||
:return: The response from the Mastodon API.
|
||||
"""
|
||||
media_ids = []
|
||||
for image in images:
|
||||
# Upload the image and get the media ID
|
||||
media_ids.append(self.upload_media(f"{settings.MEDIA_ROOT}/{image.image}", image.alt_text))
|
||||
|
||||
# Post the status with the uploaded image's media ID
|
||||
return self.post_status(status, media_ids=media_ids)
|
||||
|
||||
|
||||
def post_an_to_fedi(adoption_notice):
|
||||
client = FediClient(settings.fediverse_access_token, settings.fediverse_api_base_url)
|
||||
|
||||
context = {"adoption_notice": adoption_notice}
|
||||
status_text = render_to_string("fellchensammlung/misc/fediverse/an-post.md", context)
|
||||
images = adoption_notice.get_photos()
|
||||
|
||||
if images is not None:
|
||||
response = client.post_status_with_images(status_text, images)
|
||||
else:
|
||||
response = client.post_status(status_text)
|
||||
logging.info(response)
|
||||
post = SocialMediaPost.objects.create(adoption_notice=adoption_notice,
|
||||
platform=PlatformChoices.FEDIVERSE,
|
||||
url=response['url'], )
|
||||
return post
|
@@ -8,8 +8,9 @@ def gather_metrics_data():
|
||||
|
||||
"""Adoption notices"""
|
||||
num_adoption_notices = AdoptionNotice.objects.count()
|
||||
num_adoption_notices_active = AdoptionNotice.objects.filter(
|
||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE).count()
|
||||
adoption_notices_active = AdoptionNotice.objects.filter(
|
||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE)
|
||||
num_adoption_notices_active = adoption_notices_active.count()
|
||||
num_adoption_notices_closed = AdoptionNotice.objects.filter(
|
||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.CLOSED).count()
|
||||
num_adoption_notices_disabled = AdoptionNotice.objects.filter(
|
||||
@@ -18,6 +19,19 @@ def gather_metrics_data():
|
||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.AWAITING_ACTION).count()
|
||||
|
||||
adoption_notices_without_location = AdoptionNotice.objects.filter(location__isnull=True).count()
|
||||
|
||||
active_animals = 0
|
||||
active_animals_per_sex = {}
|
||||
for adoption_notice in adoption_notices_active:
|
||||
nps = adoption_notice.num_per_sex
|
||||
for sex in nps:
|
||||
number_of_animals = nps[sex]
|
||||
try:
|
||||
active_animals_per_sex[sex] += number_of_animals
|
||||
except KeyError:
|
||||
active_animals_per_sex[sex] = number_of_animals
|
||||
active_animals += number_of_animals
|
||||
|
||||
data = {
|
||||
'users': num_user,
|
||||
'staff': num_staff,
|
||||
@@ -29,6 +43,8 @@ def gather_metrics_data():
|
||||
'disabled': num_adoption_notices_disabled,
|
||||
'awaiting_action': num_adoption_notices_awaiting_action,
|
||||
},
|
||||
'adoption_notices_without_location': adoption_notices_without_location
|
||||
'adoption_notices_without_location': adoption_notices_without_location,
|
||||
'active_animals': active_animals,
|
||||
'active_animals_per_sex': active_animals_per_sex
|
||||
}
|
||||
return data
|
||||
|
@@ -37,6 +37,8 @@ def time_since_as_hr_string(age: datetime.timedelta) -> str:
|
||||
weeks = age.days / 7
|
||||
months = age.days / 30
|
||||
years = age.days / 365
|
||||
minutes = age.seconds / 60
|
||||
hours = age.seconds / 3600
|
||||
if years >= 1:
|
||||
text = ngettext(
|
||||
"vor einem Jahr",
|
||||
@@ -49,11 +51,14 @@ def time_since_as_hr_string(age: datetime.timedelta) -> str:
|
||||
text = _("vor %(month)d Monaten") % {"month": months}
|
||||
elif weeks >= 3:
|
||||
text = _("vor %(weeks)d Wochen") % {"weeks": weeks}
|
||||
elif days >= 1:
|
||||
text = ngettext("vor einem Tag","vor %(count)d Tagen", days,) % {"count": days,}
|
||||
elif hours >= 1:
|
||||
text = ngettext("vor einer Stunde", "vor %(count)d Stunden", hours,) % {"count": hours,}
|
||||
elif minutes >= 1:
|
||||
text = ngettext("vor einer Minute", "vor %(count)d Minuten", minutes, ) % {"count": minutes, }
|
||||
else:
|
||||
if days == 0:
|
||||
text = _("Heute")
|
||||
else:
|
||||
text = ngettext("vor einem Tag","vor %(count)d Tagen", days,) % {"count": days,}
|
||||
text = _("Gerade eben")
|
||||
return text
|
||||
|
||||
|
||||
|
57
src/fellchensammlung/tools/model_helpers.py
Normal file
57
src/fellchensammlung/tools/model_helpers.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
"""
|
||||
Helpers that MUST NOT DEPEND ON MODELS to avoid circular imports
|
||||
"""
|
||||
|
||||
|
||||
class NotificationTypeChoices(models.TextChoices):
|
||||
NEW_USER = "new_user", _("Useraccount wurde erstellt")
|
||||
NEW_REPORT_AN = "new_report_an", _("Vermittlung wurde gemeldet")
|
||||
NEW_REPORT_COMMENT = "new_report_comment", _("Kommentar wurde gemeldet")
|
||||
AN_IS_TO_BE_CHECKED = "an_is_to_be_checked", _("Vermittlung muss überprüft werden")
|
||||
AN_WAS_DEACTIVATED = "an_was_deactivated", _("Vermittlung wurde deaktiviert")
|
||||
AN_FOR_SEARCH_FOUND = "an_for_search_found", _("Vermittlung für Suche gefunden")
|
||||
NEW_COMMENT = "new_comment", _("Neuer Kommentar")
|
||||
|
||||
|
||||
class NotificationDisplayMapping:
|
||||
def __init__(self, email_html_template, email_plain_template, web_partial):
|
||||
self.email_html_template = email_html_template
|
||||
self.email_plain_template = email_plain_template
|
||||
self.web_partial = web_partial
|
||||
|
||||
|
||||
report_mapping = NotificationDisplayMapping(
|
||||
email_html_template='fellchensammlung/mail/notifications/report.html',
|
||||
email_plain_template='fellchensammlung/mail/notifications/report.txt',
|
||||
web_partial="fellchensammlung/partials/notifications/body-new-report.html"
|
||||
)
|
||||
# ndm = notification display mapping
|
||||
ndm = {NotificationTypeChoices.NEW_USER: NotificationDisplayMapping(
|
||||
email_html_template='fellchensammlung/mail/notifications/report.html',
|
||||
email_plain_template="fellchensammlung/mail/notifications/report.txt",
|
||||
web_partial="fellchensammlung/partials/notifications/body-new-user.html"),
|
||||
NotificationTypeChoices.NEW_COMMENT: NotificationDisplayMapping(
|
||||
email_html_template='fellchensammlung/mail/notifications/new-comment.html',
|
||||
email_plain_template='fellchensammlung/mail/notifications/new-comment.txt',
|
||||
web_partial="fellchensammlung/partials/notifications/body-new-comment.html"),
|
||||
NotificationTypeChoices.NEW_REPORT_AN: report_mapping,
|
||||
NotificationTypeChoices.NEW_REPORT_COMMENT: report_mapping,
|
||||
NotificationTypeChoices.AN_IS_TO_BE_CHECKED: NotificationDisplayMapping(
|
||||
email_html_template='fellchensammlung/mail/notifications/an-to-be-checked.html',
|
||||
email_plain_template='fellchensammlung/mail/notifications/an-to-be-checked.txt',
|
||||
web_partial='fellchensammlung/partials/notifications/body-an-to-be-checked.html'
|
||||
),
|
||||
NotificationTypeChoices.AN_WAS_DEACTIVATED: NotificationDisplayMapping(
|
||||
email_html_template='fellchensammlung/mail/notifications/an-deactivated.html',
|
||||
email_plain_template='fellchensammlung/mail/notifications/an-deactivated.txt',
|
||||
web_partial='fellchensammlung/partials/notifications/body-an-deactivated.html'
|
||||
),
|
||||
NotificationTypeChoices.AN_FOR_SEARCH_FOUND: NotificationDisplayMapping(
|
||||
email_html_template='fellchensammlung/mail/notifications/an-for-search-found.html',
|
||||
email_plain_template='fellchensammlung/mail/notifications/an-for-search-found.txt',
|
||||
web_partial='fellchensammlung/partials/notifications/body-an-for-search.html'
|
||||
)
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
from fellchensammlung.models import User, Notification, TrustLevel, NotificationTypeChoices
|
||||
from fellchensammlung.models import User, Notification, TrustLevel
|
||||
from fellchensammlung.models import NotificationTypeChoices as ntc
|
||||
|
||||
|
||||
def notify_of_AN_to_be_checked(adoption_notice):
|
||||
@@ -8,7 +9,7 @@ def notify_of_AN_to_be_checked(adoption_notice):
|
||||
for user in users_to_notify:
|
||||
Notification.objects.create(adoption_notice=adoption_notice,
|
||||
user_to_notify=user,
|
||||
notification_type=NotificationTypeChoices.AN_IS_TO_BE_CHECKED,
|
||||
notification_type=ntc.AN_IS_TO_BE_CHECKED,
|
||||
title=f" Prüfe Vermittlung {adoption_notice}",
|
||||
text=f"{adoption_notice} muss geprüft werden bevor sie veröffentlicht wird.",
|
||||
)
|
||||
|
@@ -7,12 +7,14 @@ from . import views, registration_views
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from .sitemap import StaticViewSitemap, AdoptionNoticeSitemap, AnimalSitemap
|
||||
from .sitemap import StaticViewSitemap, AdoptionNoticeSitemap, RescueOrganizationSitemap, SearchSitemap
|
||||
|
||||
sitemaps = {
|
||||
"static": StaticViewSitemap,
|
||||
"vermittlungen": AdoptionNoticeSitemap,
|
||||
"tiere": AnimalSitemap,
|
||||
"tierschutzorganisationen": RescueOrganizationSitemap,
|
||||
"orte": SearchSitemap
|
||||
|
||||
}
|
||||
|
||||
urlpatterns = [
|
||||
@@ -35,10 +37,14 @@ urlpatterns = [
|
||||
# ex: /adoption_notice/2/add-animal
|
||||
path("vermittlung/<int:adoption_notice_id>/add-animal", views.adoption_notice_add_animal,
|
||||
name="adoption-notice-add-animal"),
|
||||
path("vermittlung/<int:adoption_notice_id>/close", views.deactivate_an,
|
||||
name="adoption-notice-close"),
|
||||
|
||||
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
||||
path("tierschutzorganisationen/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
||||
name="rescue-organization-detail"),
|
||||
path("tierschutzorganisationen/spezialisierung/<int:species_id>", views.specialized_rescues,
|
||||
name="specialized-rescue-organizations"),
|
||||
|
||||
# ex: /search/
|
||||
path("suchen/", views.search, name="search"),
|
||||
@@ -52,6 +58,7 @@ urlpatterns = [
|
||||
path("impressum/", views.imprint, name="imprint"),
|
||||
path("terms-of-service/", views.terms_of_service, name="terms-of-service"),
|
||||
path("datenschutz/", views.privacy, name="privacy"),
|
||||
path("ratten-kaufen/", views.buying, name="buying"),
|
||||
|
||||
################
|
||||
## Moderation ##
|
||||
@@ -75,6 +82,7 @@ urlpatterns = [
|
||||
# ex: user/1
|
||||
path("user/<int:user_id>/", views.user_by_id, name="user-detail"),
|
||||
path("user/me/", views.my_profile, name="user-me"),
|
||||
path("user/notifications/", views.my_notifications, name="user-notifications"),
|
||||
path('user/me/export/', views.export_own_profile, name='user-me-export'),
|
||||
|
||||
path('accounts/register/',
|
||||
|
@@ -14,19 +14,21 @@ from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.serializers import serialize
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import json
|
||||
import requests
|
||||
|
||||
from .mail import mail_admins_new_report
|
||||
from .mail import notify_mods_new_report
|
||||
from notfellchen import settings
|
||||
|
||||
from fellchensammlung import logger
|
||||
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
|
||||
User, Location, AdoptionNoticeStatus, Subscriptions, Notification, RescueOrganization, \
|
||||
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, \
|
||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices
|
||||
ImportantLocation, SpeciesSpecificURL, NotificationTypeChoices, SocialMediaPost
|
||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, \
|
||||
CommentForm, ReportCommentForm, AnimalForm, AdoptionNoticeFormAutoAnimal, SpeciesURLForm, RescueOrgInternalComment
|
||||
from .models import Language, Announcement
|
||||
from .tools import i18n
|
||||
from .tools.fedi import post_an_to_fedi
|
||||
from .tools.geo import GeoAPI, zoom_level_for_radius
|
||||
from .tools.metrics import gather_metrics_data
|
||||
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices, \
|
||||
@@ -85,8 +87,27 @@ def change_language(request):
|
||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
||||
|
||||
|
||||
def handle_an_check_actions(request, action, adoption_notice=None):
|
||||
if adoption_notice is None:
|
||||
adoption_notice = AdoptionNotice.objects.get(id=request.POST.get("adoption_notice_id"))
|
||||
edit_permission = request.user == adoption_notice.owner or user_is_trust_level_or_above(request.user,
|
||||
TrustLevel.MODERATOR)
|
||||
|
||||
# Check if the user is permitted to perform the actions
|
||||
if action in ("checked_inactive", "checked_active") and not request.user.is_authenticated or not edit_permission:
|
||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
||||
|
||||
if action == "checked_inactive":
|
||||
adoption_notice.set_closed()
|
||||
elif action == "checked_active":
|
||||
print("dads")
|
||||
adoption_notice.set_active()
|
||||
return None
|
||||
|
||||
|
||||
def adoption_notice_detail(request, adoption_notice_id):
|
||||
adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id)
|
||||
adoption_notice_meta = adoption_notice._meta
|
||||
if request.user.is_authenticated:
|
||||
try:
|
||||
subscription = Subscriptions.objects.get(owner=request.user, adoption_notice=adoption_notice)
|
||||
@@ -98,6 +119,7 @@ def adoption_notice_detail(request, adoption_notice_id):
|
||||
has_edit_permission = user_is_owner_or_trust_level(request.user, adoption_notice)
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get("action")
|
||||
handle_an_check_actions(request, action, adoption_notice)
|
||||
if request.user.is_authenticated:
|
||||
if action == "comment":
|
||||
comment_form = CommentForm(request.POST)
|
||||
@@ -143,7 +165,8 @@ def adoption_notice_detail(request, adoption_notice_id):
|
||||
else:
|
||||
comment_form = CommentForm(instance=adoption_notice)
|
||||
context = {"adoption_notice": adoption_notice, "comment_form": comment_form, "user": request.user,
|
||||
"has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed}
|
||||
"has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed,
|
||||
"adoption_notice_meta": adoption_notice_meta}
|
||||
return render(request, 'fellchensammlung/details/detail-adoption-notice.html', context=context)
|
||||
|
||||
|
||||
@@ -454,6 +477,11 @@ def privacy(request):
|
||||
return render_text(request, text)
|
||||
|
||||
|
||||
def buying(request):
|
||||
text = i18n.get_text_by_language("buying")
|
||||
return render_text(request, text)
|
||||
|
||||
|
||||
def terms_of_service(request):
|
||||
text = i18n.get_text_by_language("terms_of_service")
|
||||
rules = Rule.objects.all()
|
||||
@@ -478,8 +506,7 @@ def report_adoption(request, adoption_notice_id):
|
||||
report_instance.status = Report.WAITING
|
||||
report_instance.save()
|
||||
form.save_m2m()
|
||||
mail_admins_new_report(report_instance)
|
||||
print("dada")
|
||||
notify_mods_new_report(report_instance, NotificationTypeChoices.NEW_REPORT_AN)
|
||||
return redirect(reverse("report-detail-success", args=[report_instance.pk], ))
|
||||
else:
|
||||
form = ReportAdoptionNoticeForm()
|
||||
@@ -499,7 +526,7 @@ def report_comment(request, comment_id):
|
||||
report_instance.status = Report.WAITING
|
||||
report_instance.save()
|
||||
form.save_m2m()
|
||||
mail_admins_new_report(report_instance)
|
||||
notify_mods_new_report(report_instance, NotificationTypeChoices.NEW_REPORT_COMMENT)
|
||||
return redirect(reverse("report-detail-success", args=[report_instance.pk], ))
|
||||
else:
|
||||
form = ReportCommentForm()
|
||||
@@ -549,6 +576,27 @@ def user_by_id(request, user_id):
|
||||
return user_detail(request, user)
|
||||
|
||||
|
||||
def process_notification_actions(request, action):
|
||||
"""
|
||||
As multiple views allow to mark notifications as read, this function can be used to process these actions
|
||||
|
||||
The function allows users to mark only their own notifications as read.
|
||||
"""
|
||||
if action == "notification_mark_read":
|
||||
notification_id = request.POST.get("notification_id")
|
||||
|
||||
notification = Notification.objects.get(pk=notification_id)
|
||||
# Ensures a user can only mark their own notifications as read
|
||||
if not notification.user_to_notify == request.user:
|
||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
||||
notification.mark_read()
|
||||
elif action == "notification_mark_all_read":
|
||||
notifications = Notification.objects.filter(user_to_notify=request.user, read=False)
|
||||
for notification in notifications:
|
||||
notification.mark_read()
|
||||
return None
|
||||
|
||||
|
||||
@login_required()
|
||||
def my_profile(request):
|
||||
if request.method == 'POST':
|
||||
@@ -562,16 +610,8 @@ def my_profile(request):
|
||||
user.save()
|
||||
|
||||
action = request.POST.get("action")
|
||||
if action == "notification_mark_read":
|
||||
notification_id = request.POST.get("notification_id")
|
||||
|
||||
notification = Notification.objects.get(pk=notification_id)
|
||||
notification.mark_read()
|
||||
elif action == "notification_mark_all_read":
|
||||
notifications = Notification.objects.filter(user=request.user, mark_read=False)
|
||||
for notification in notifications:
|
||||
notification.mark_read()
|
||||
elif action == "search_subscription_delete":
|
||||
process_notification_actions(request, action)
|
||||
if action == "search_subscription_delete":
|
||||
search_subscription_id = request.POST.get("search_subscription_id")
|
||||
SearchSubscription.objects.get(pk=search_subscription_id).delete()
|
||||
logging.info(f"Deleted subscription {search_subscription_id}")
|
||||
@@ -583,6 +623,19 @@ def my_profile(request):
|
||||
return user_detail(request, request.user, token)
|
||||
|
||||
|
||||
@login_required()
|
||||
def my_notifications(request):
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get("action")
|
||||
process_notification_actions(request, action)
|
||||
|
||||
context = {"notifications_unread": Notification.objects.filter(user_to_notify=request.user, read=False).order_by(
|
||||
"-created_at"),
|
||||
"notifications_read_last": Notification.objects.filter(user_to_notify=request.user,
|
||||
read=True).order_by("-read_at")}
|
||||
return render(request, 'fellchensammlung/notifications.html', context=context)
|
||||
|
||||
|
||||
@user_passes_test(user_is_trust_level_or_above)
|
||||
def modqueue(request):
|
||||
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
||||
@@ -593,16 +646,11 @@ def modqueue(request):
|
||||
@login_required
|
||||
def updatequeue(request):
|
||||
if request.method == "POST":
|
||||
adoption_notice = AdoptionNotice.objects.get(id=request.POST.get("adoption_notice_id"))
|
||||
edit_permission = request.user == adoption_notice.owner or user_is_trust_level_or_above(request.user,
|
||||
TrustLevel.MODERATOR)
|
||||
if not edit_permission:
|
||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
||||
action = request.POST.get("action")
|
||||
if action == "checked_inactive":
|
||||
adoption_notice.set_closed()
|
||||
if action == "checked_active":
|
||||
adoption_notice.set_active()
|
||||
|
||||
# This function handles the activation and deactivation of ANs
|
||||
# Separate function because it's used in multiple places
|
||||
handle_an_check_actions(request, action)
|
||||
|
||||
if user_is_trust_level_or_above(request.user, TrustLevel.MODERATOR):
|
||||
last_checked_adoption_list = AdoptionNotice.objects.order_by("last_checked")
|
||||
@@ -697,8 +745,12 @@ def external_site_warning(request, template_name='fellchensammlung/external-site
|
||||
return render(request, template_name, context=context)
|
||||
|
||||
|
||||
def list_rescue_organizations(request, template='fellchensammlung/animal-shelters.html'):
|
||||
rescue_organizations = RescueOrganization.objects.all()
|
||||
def list_rescue_organizations(request, species=None, template='fellchensammlung/animal-shelters.html'):
|
||||
if species is None:
|
||||
rescue_organizations = RescueOrganization.objects.all()
|
||||
else:
|
||||
rescue_organizations = RescueOrganization.objects.filter(specializations=species)
|
||||
|
||||
paginator = Paginator(rescue_organizations, 10)
|
||||
|
||||
page_number = request.GET.get("page")
|
||||
@@ -717,6 +769,11 @@ def list_rescue_organizations(request, template='fellchensammlung/animal-shelter
|
||||
return render(request, template, context=context)
|
||||
|
||||
|
||||
def specialized_rescues(request, species_id):
|
||||
species = get_object_or_404(Species, pk=species_id)
|
||||
return list_rescue_organizations(request, species)
|
||||
|
||||
|
||||
def detail_view_rescue_organization(request, rescue_organization_id,
|
||||
template='fellchensammlung/details/detail-rescue-organization.html'):
|
||||
org = RescueOrganization.objects.get(pk=rescue_organization_id)
|
||||
@@ -764,12 +821,17 @@ def rescue_organization_check(request, context=None):
|
||||
if comment_form.is_valid():
|
||||
comment_form.save()
|
||||
|
||||
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).order_by("last_checked")[:10]
|
||||
rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
|
||||
ongoing_communication=False).order_by("last_checked")[:3]
|
||||
rescue_orgs_with_ongoing_communication = RescueOrganization.objects.filter(ongoing_communication=True).order_by(
|
||||
"updated_at")
|
||||
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
||||
rescue_orgs_to_comment = rescue_orgs_to_check | rescue_orgs_with_ongoing_communication | rescue_orgs_last_checked
|
||||
# Prepare a form for each organization
|
||||
comment_forms = {
|
||||
org.id: RescueOrgInternalComment(instance=org) for org in rescue_orgs_to_check
|
||||
org.id: RescueOrgInternalComment(instance=org) for org in rescue_orgs_to_comment
|
||||
}
|
||||
rescue_orgs_last_checked = RescueOrganization.objects.filter().order_by("-last_checked")[:10]
|
||||
|
||||
timeframe = timezone.now().date() - timedelta(days=14)
|
||||
num_rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False).filter(
|
||||
last_checked__lt=timeframe).count()
|
||||
@@ -787,6 +849,8 @@ def rescue_organization_check(request, context=None):
|
||||
context["num_rescue_orgs_to_check"] = num_rescue_orgs_to_check
|
||||
context["percentage_checked"] = percentage_checked
|
||||
context["num_rescue_orgs_checked"] = num_rescue_orgs_checked
|
||||
context["rescue_orgs_with_ongoing_communication"] = rescue_orgs_with_ongoing_communication
|
||||
context["set_internal_comment_available"] = True
|
||||
return render(request, 'fellchensammlung/rescue-organization-check.html', context=context)
|
||||
|
||||
|
||||
@@ -797,7 +861,7 @@ def rescue_organization_check_dq(request):
|
||||
DQ = data quality
|
||||
"""
|
||||
context = {"set_species_url_available": True,
|
||||
"set_internal_comment_available": True,
|
||||
"dq": True,
|
||||
"species_url_form": SpeciesURLForm,
|
||||
"internal_comment_form": RescueOrgInternalComment}
|
||||
return rescue_organization_check(request, context)
|
||||
@@ -805,4 +869,38 @@ def rescue_organization_check_dq(request):
|
||||
|
||||
@user_passes_test(user_is_trust_level_or_above)
|
||||
def moderation_tools_overview(request):
|
||||
return render(request, 'fellchensammlung/mod-tool-overview.html')
|
||||
context = None
|
||||
if request.method == "POST":
|
||||
action = request.POST.get("action")
|
||||
if action == "post_to_fedi":
|
||||
adoption_notice = SocialMediaPost.get_an_to_post()
|
||||
if adoption_notice is not None:
|
||||
try:
|
||||
post = post_an_to_fedi(adoption_notice)
|
||||
context = {"action_was_posting": True, "post": post, "posted_successfully": True}
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
logging.error(f"Could not post fediverse post: {e}")
|
||||
context = {"action_was_posting": True,
|
||||
"posted_successfully": False,
|
||||
"error_message": _("Verbindungsfehler. Vermittlung wurde nicht gepostet")}
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logging.error(f"Could not post fediverse post: {e}")
|
||||
context = {"action_was_posting": True,
|
||||
"posted_successfully": False,
|
||||
"error_message": _("Fehler beim Posten. Vermittlung wurde nicht gepostet. Das kann "
|
||||
"z.B. an falschen Zugangsdaten liegen. Kontaktieren einen Admin.")}
|
||||
else:
|
||||
context = {"action_was_posting": True,
|
||||
"posted_successfully": False,
|
||||
"error_message": _("Keine Vermittlung zum Posten gefunden.")}
|
||||
return render(request, 'fellchensammlung/mod-tool-overview.html', context=context)
|
||||
|
||||
|
||||
def deactivate_an(request, adoption_notice_id):
|
||||
adoption_notice = get_object_or_404(AdoptionNotice, pk=adoption_notice_id)
|
||||
if request.method == "POST":
|
||||
reason_for_closing = request.POST.get("reason_for_closing")
|
||||
adoption_notice.set_closed(reason_for_closing)
|
||||
return redirect(reverse("adoption-notice-detail", args=[adoption_notice.pk], ))
|
||||
context = {"adoption_notice": adoption_notice, }
|
||||
return render(request, 'fellchensammlung/misc/deactivate-an.html', context=context)
|
||||
|
@@ -9,7 +9,7 @@ msgstr ""
|
||||
"Project-Id-Version: Notfellchen\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-19 15:51+0000\n"
|
||||
"PO-Revision-Date: 2025-06-19 17:52+0200\n"
|
||||
"PO-Revision-Date: 2025-07-17 15:55+0200\n"
|
||||
"Last-Translator: Julian-Samuel <Gebühr>\n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en\n"
|
||||
@@ -343,7 +343,7 @@ msgstr "Adoption Notice deactivated:"
|
||||
|
||||
#: src/fellchensammlung/models.py:485
|
||||
msgid "Die folgende Vermittlung wurde deaktiviert: "
|
||||
msgstr "The following adoption notice was deactivated:"
|
||||
msgstr "The following adoption notice was deactivated: "
|
||||
|
||||
#: src/fellchensammlung/models.py:585 src/fellchensammlung/models.py:593
|
||||
msgid "Weiblich"
|
||||
@@ -562,7 +562,7 @@ msgstr "Adoption notices of Organization"
|
||||
#: src/fellchensammlung/templates/fellchensammlung/details/detail-rescue-organization.html:47
|
||||
#: src/fellchensammlung/templates/fellchensammlung/lists/list-adoption-notices.html:11
|
||||
msgid "Keine Vermittlungen gefunden."
|
||||
msgstr "No adoption notices found"
|
||||
msgstr "No adoption notices found."
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/details/detail-user.html:11
|
||||
msgid "Profil verwalten"
|
||||
@@ -680,7 +680,7 @@ msgstr "Report problems"
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/footer.html:77
|
||||
msgid "Code"
|
||||
msgstr "code"
|
||||
msgstr "Code"
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/footer.html:84
|
||||
msgid "Hilfreiche Links"
|
||||
@@ -903,7 +903,7 @@ msgstr "Modqueue"
|
||||
msgid ""
|
||||
"Erlaube oder blockiere Vermittlungsanzeigen die bisher noch zurückgehalten "
|
||||
"werden "
|
||||
msgstr ""
|
||||
msgstr "Allow or block adoption notices that are waiting in queue"
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/partials/partial-adoption-notice.html:12
|
||||
msgid "Notfellchen"
|
||||
@@ -956,7 +956,7 @@ msgstr "You need to log in to comment"
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/partials/partial-report.html:4
|
||||
msgid "Meldung von "
|
||||
msgstr "Report of"
|
||||
msgstr "Report of "
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/partials/partial-report.html:7
|
||||
msgid "Regeln gegen die Verstoßen wurde"
|
||||
@@ -1049,7 +1049,7 @@ msgstr "Deactivated adoption notices to check"
|
||||
|
||||
#: src/fellchensammlung/templates/fellchensammlung/updatequeue.html:13
|
||||
msgid "Aktive Vermittlungen zur Überprüfung"
|
||||
msgstr "Active "
|
||||
msgstr "Active Adoption Notices to check"
|
||||
|
||||
#: src/fellchensammlung/tools/misc.py:42
|
||||
#, python-format
|
||||
|
@@ -1,4 +1,4 @@
|
||||
__version__ = "1.0.1"
|
||||
__version__ = "1.1.0"
|
||||
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
@@ -24,6 +24,11 @@ app.conf.beat_schedule = {
|
||||
'task': 'admin.deactivate_404_adoption_notices',
|
||||
'schedule': crontab(hour=3),
|
||||
},
|
||||
'daily-fedi-post': {
|
||||
'task': 'social_media.post_fedi',
|
||||
'schedule': crontab(hour=19),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
if settings.HEALTHCHECKS_URL is not None and settings.HEALTHCHECKS_URL != "":
|
||||
|
@@ -118,6 +118,12 @@ else:
|
||||
EMAIL_USE_TLS = config.getboolean('mail', 'tls', fallback=False)
|
||||
EMAIL_USE_SSL = config.getboolean('mail', 'ssl', fallback=False)
|
||||
|
||||
""" Fediverse """
|
||||
fediverse_enabled = config.get('fediverse', 'enabled', fallback=False)
|
||||
if fediverse_enabled:
|
||||
fediverse_api_base_url = config.get('fediverse', 'api_base_url')
|
||||
fediverse_access_token = config.get('fediverse', 'access_token')
|
||||
|
||||
"""USER MANAGEMENT"""
|
||||
AUTH_USER_MODEL = "fellchensammlung.User"
|
||||
ACCOUNT_ACTIVATION_DAYS = 7 # One-week activation window
|
||||
|
Reference in New Issue
Block a user