Compare commits

..

No commits in common. "3eb7dbe9843dfa6fecf653477d59464e8e557936" and "bf54bc5d51a69017c975c39b75e8e78a761521bb" have entirely different histories.

33 changed files with 165 additions and 518 deletions

View File

@ -35,7 +35,7 @@ dependencies = [
"markdown",
"Pillow",
"django-registration",
"psycopg2-binary",
"psycopg2",
"django-crispy-forms",
"crispy-bootstrap4",
"djangorestframework",

View File

@ -15,4 +15,3 @@ class FellchensammlungConfig(AppConfig):
except Permission.DoesNotExist:
pass
post_migrate.connect(ensure_languages, sender=self)
import fellchensammlung.receivers

View File

@ -9,14 +9,6 @@ from django.utils.translation import gettext_lazy as _
from notfellchen.settings import MEDIA_URL
def animal_validator(value: str):
value = value.lower()
animal_list = ["ratte", "farbratte", "katze", "hund", "kaninchen", "hase", "kuh", "fuchs", "cow", "rat", "cat",
"dog", "rabbit", "fox", "fancy rat"]
if value not in animal_list:
raise forms.ValidationError(_("Dieses Tier kenne ich nicht. Probier ein anderes"))
class DateInput(forms.DateInput):
input_type = 'date'
@ -51,7 +43,6 @@ class AdoptionNoticeForm(forms.ModelForm):
'group_only',
'searching_since',
'location_string',
'organization',
'description',
'further_information',
),
@ -59,19 +50,19 @@ class AdoptionNoticeForm(forms.ModelForm):
class Meta:
model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
"organization"]
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"]
class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm):
class Meta:
model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string", "organization"]
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"]
widgets = {
'searching_since': DateInput(),
}
class AnimalForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
if 'in_adoption_notice_creation_flow' in kwargs:
@ -100,7 +91,6 @@ class AnimalFormWithDateWidget(AnimalForm):
'date_of_birth': DateInput(),
}
class AdoptionNoticeFormWithDateWidgetAutoAnimal(AdoptionNoticeFormWithDateWidget):
def __init__(self, *args, **kwargs):
super(AdoptionNoticeFormWithDateWidgetAutoAnimal, self).__init__(*args, **kwargs)
@ -169,8 +159,6 @@ class CustomRegistrationForm(RegistrationForm):
class Meta(RegistrationForm.Meta):
model = User
captcha = forms.CharField(validators=[animal_validator], label=_("Nenne eine bekannte Tierart"), help_text=_("Bitte nenne hier eine bekannte Tierart (z.B. ein Tier das an der Leine geführt wird). Das Fragen wir dich um sicherzustellen, dass du kein Roboter bist."))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
@ -181,7 +169,7 @@ class CustomRegistrationForm(RegistrationForm):
def _get_distances():
return {i: i for i in [20, 50, 100, 200, 500]}
return {i: i for i in [10, 20, 50, 100, 200, 500]}
class AdoptionNoticeSearchForm(forms.Form):

View File

@ -1,10 +1,15 @@
from venv import create
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
from django.conf import settings
from django.core import mail
from fellchensammlung.models import User, CommentNotification, BaseNotification, TrustLevel
from django.db.models import Q, Min
from fellchensammlung.models import User
from notfellchen.settings import host
NEWLINE = "\r\n"
@ -12,7 +17,7 @@ NEWLINE = "\r\n"
def mail_admins_new_report(report):
subject = _("Neue Meldung")
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]):
greeting = _("Moin,") + "{NEWLINE}"
new_report_text = _("es wurde ein Regelverstoß gemeldet.") + "{NEWLINE}"
if len(report.reported_broken_rules.all()) > 0:
@ -29,15 +34,23 @@ def mail_admins_new_report(report):
link_text = f"Um alle Details zu sehen, geh bitte auf: {report_url}"
body_text = greeting + new_report_text + reported_rules_text + comment_text + link_text
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email])
print("Sending email to ", moderator.email)
message.send()
def send_notification_email(notification_pk):
try:
notification = CommentNotification.objects.get(pk=notification_pk)
except CommentNotification.DoesNotExist:
notification = BaseNotification.objects.get(pk=notification_pk)
subject = f"🔔 {notification.title}"
body_text = notification.text
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [notification.user.email])
message.send()
@receiver(post_save, sender=User)
def mail_admins_new_member(sender, instance: User, created: bool, **kwargs):
if not created:
return
subject = _("Neuer User") + f": {instance.username}"
for moderator in User.objects.filter(trust_level__gt=User.TRUST_LEVEL[User.MODERATOR]):
greeting = _("Moin,") + "{NEWLINE}"
new_report_text = _("es hat sich eine neue Person registriert.") + "{NEWLINE}"
user_detail_text = _("Username") + f": {instance.username}{NEWLINE}" + _(
"E-Mail") + f": {instance.email}{NEWLINE}"
user_url = "https://" + host + instance.get_absolute_url()
link_text = f"Um alle Details zu sehen, geh bitte auf: {user_url}"
body_text = greeting + new_report_text + user_detail_text + link_text
message = mail.EmailMessage(subject, body_text, settings.DEFAULT_FROM_EMAIL, [moderator.email])
print("Sending email to ", moderator.email)
message.send()

View File

@ -7,7 +7,7 @@ from fellchensammlung import baker_recipes
from model_bakery import baker
from fellchensammlung.models import AdoptionNotice, Species, Animal, Image, ModerationAction, User, Rule, \
Report, Comment, ReportAdoptionNotice, TrustLevel
Report, Comment, ReportAdoptionNotice
class Command(BaseCommand):
@ -101,10 +101,10 @@ class Command(BaseCommand):
User.objects.create_user('test', password='foobar')
admin1 = User.objects.create_superuser(username="admin", password="admin", email="admin1@example.org",
trust_level=TrustLevel.ADMIN)
trust_level=User.TRUST_LEVEL[User.ADMIN])
mod1 = User.objects.create_user(username="mod1", password="mod", email="mod1@example.org",
trust_level=TrustLevel.MODERATOR)
trust_level=User.TRUST_LEVEL[User.MODERATOR])
comment1 = baker.make(Comment, user=admin1, text="This is a comment", adoption_notice=adoption1)
comment2 = baker.make(Comment,

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-13 15:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0015_rescueorganization_comment'),
]
operations = [
migrations.AddField(
model_name='rescueorganization',
name='phone_number',
field=models.CharField(blank=True, max_length=15, null=True, verbose_name='Telefonnummer'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-14 06:42
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0016_rescueorganization_phone_number'),
]
operations = [
migrations.AddField(
model_name='user',
name='organization_affiliation',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='fellchensammlung.rescueorganization', verbose_name='Organisation'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-14 17:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0017_user_organization_affiliation'),
]
operations = [
migrations.AddField(
model_name='rescueorganization',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='Beschreibung'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-14 18:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0018_rescueorganization_description'),
]
operations = [
migrations.RenameField(
model_name='rescueorganization',
old_name='comment',
new_name='internal_comment',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-14 18:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0019_rename_comment_rescueorganization_internal_comment'),
]
operations = [
migrations.AlterField(
model_name='rescueorganization',
name='internal_comment',
field=models.TextField(blank=True, null=True, verbose_name='Interner Kommentar'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-14 20:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0020_alter_rescueorganization_internal_comment'),
]
operations = [
migrations.AddField(
model_name='user',
name='reason_for_signup',
field=models.TextField(default='-', verbose_name='Grund für die Registrierung'),
preserve_default=False,
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-20 18:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0021_user_reason_for_signup'),
]
operations = [
migrations.AlterField(
model_name='user',
name='reason_for_signup',
field=models.TextField(help_text="Wir würden gerne wissen warum du dich registriertst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben.", verbose_name='Grund für die Registrierung'),
),
migrations.AlterField(
model_name='user',
name='trust_level',
field=models.IntegerField(choices=[(1, 'Member'), (2, 'Coordinator'), (3, 'Moderator'), (4, 'Admin')], default=1),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.1 on 2024-11-20 19:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0022_alter_user_reason_for_signup_alter_user_trust_level'),
]
operations = [
migrations.AddField(
model_name='user',
name='email_notifications',
field=models.BooleanField(default=True, verbose_name='Benachrichtigung per E-Mail'),
),
]

View File

@ -34,6 +34,86 @@ class Language(models.Model):
verbose_name_plural = _('Sprachen')
class User(AbstractUser):
"""
Model that holds a user's profile, including the django user model
The trust levels act as permission system and can be displayed as a badge for the user
"""
# Admins can perform all actions and have the highest trust associated with them
# Moderators can make moderation decisions regarding the deletion of content
# Coordinators can create adoption notices without them being checked
# Members can create adoption notices that must be activated
ADMIN = "admin"
MODERATOR = "Moderator"
COORDINATOR = "Koordinator*in"
MEMBER = "Mitglied"
TRUST_LEVEL = {
ADMIN: 4,
MODERATOR: 3,
COORDINATOR: 2,
MEMBER: 1,
}
preferred_language = models.ForeignKey(Language, on_delete=models.PROTECT, null=True, blank=True,
verbose_name=_('Bevorzugte Sprache'))
trust_level = models.IntegerField(choices=TRUST_LEVEL, default=TRUST_LEVEL[MEMBER])
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _('Nutzer*in')
verbose_name_plural = _('Nutzer*innen')
def get_absolute_url(self):
return reverse("user-detail", args=[str(self.pk)])
def get_notifications_url(self):
return self.get_absolute_url()
def get_num_unread_notifications(self):
return BaseNotification.objects.filter(user=self, read=False).count()
@property
def adoption_notices(self):
return AdoptionNotice.objects.filter(owner=self)
@property
def owner(self):
return self
class Image(models.Model):
image = models.ImageField(upload_to='images')
alt_text = models.TextField(max_length=2000)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.alt_text
@property
def as_html(self):
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 Location(models.Model):
place_id = models.IntegerField()
latitude = models.FloatField()
@ -106,104 +186,10 @@ class RescueOrganization(models.Model):
facebook = models.URLField(null=True, blank=True, verbose_name=_('Facebook Profil'))
fediverse_profile = models.URLField(null=True, blank=True, verbose_name=_('Fediverse Profil'))
email = models.EmailField(null=True, blank=True, verbose_name=_('E-Mail'))
phone_number = models.CharField(max_length=15, null=True, blank=True, verbose_name=_('Telefonnummer'))
website = models.URLField(null=True, blank=True, verbose_name=_('Website'))
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
internal_comment = models.TextField(verbose_name=_("Interner Kommentar"), null=True, blank=True, )
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
def get_absolute_url(self):
return reverse("rescue-organization-detail", args=[str(self.pk)])
@property
def adoption_notices(self):
return AdoptionNotice.objects.filter(organization=self)
# Admins can perform all actions and have the highest trust associated with them
# Moderators can make moderation decisions regarding the deletion of content
# Coordinators can create adoption notices without them being checked
# Members can create adoption notices that must be activated
class TrustLevel(models.IntegerChoices):
MEMBER = 1, 'Member'
COORDINATOR = 2, 'Coordinator'
MODERATOR = 3, 'Moderator'
ADMIN = 4, 'Admin'
class User(AbstractUser):
"""
Model that holds a user's profile, including the django user model
The trust levels act as permission system and can be displayed as a badge for the user
"""
trust_level = models.IntegerField(
choices=TrustLevel.choices,
default=TrustLevel.MEMBER, # Default to the lowest trust level
)
preferred_language = models.ForeignKey(Language, on_delete=models.PROTECT, null=True, blank=True,
verbose_name=_('Bevorzugte Sprache'))
updated_at = models.DateTimeField(auto_now=True)
organization_affiliation = models.ForeignKey(RescueOrganization, on_delete=models.PROTECT, null=True, blank=True,
verbose_name=_('Organisation'))
reason_for_signup = models.TextField(verbose_name=_("Grund für die Registrierung"), help_text=_(
"Wir würden gerne wissen warum du dich registriertst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben."))
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
REQUIRED_FIELDS = ["reason_for_signup", "email"]
class Meta:
verbose_name = _('Nutzer*in')
verbose_name_plural = _('Nutzer*innen')
def get_absolute_url(self):
return reverse("user-detail", args=[str(self.pk)])
def get_notifications_url(self):
return self.get_absolute_url()
def get_num_unread_notifications(self):
return BaseNotification.objects.filter(user=self, read=False).count()
@property
def adoption_notices(self):
return AdoptionNotice.objects.filter(owner=self)
@property
def owner(self):
return self
class Image(models.Model):
image = models.ImageField(upload_to='images')
alt_text = models.TextField(max_length=2000)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.alt_text
@property
def as_html(self):
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')
comment = models.TextField(verbose_name=_("Kommentar"), null=True, blank=True,)
class AdoptionNotice(models.Model):
@ -253,6 +239,7 @@ class AdoptionNotice(models.Model):
else:
return "mixed"
@property
def comments(self):
return Comment.objects.filter(adoption_notice=self)
@ -728,6 +715,7 @@ class CommentNotification(BaseNotification):
@property
def url(self):
print(f"URL: self.comment.get_absolute_url()")
return self.comment.get_absolute_url

View File

@ -1,37 +0,0 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from fellchensammlung.models import BaseNotification, CommentNotification, User, TrustLevel
from .tasks import task_send_notification_email
from notfellchen.settings import host
from django.utils.translation import gettext_lazy as _
@receiver(post_save, sender=CommentNotification)
def comment_notification_receiver(sender, instance: BaseNotification, created: bool, **kwargs):
base_notification_receiver(sender, instance, created, **kwargs)
@receiver(post_save, sender=BaseNotification)
def base_notification_receiver(sender, instance: BaseNotification, created: bool, **kwargs):
if not created or not instance.user.email_notifications:
return
else:
task_send_notification_email.delay(instance.pk)
@receiver(post_save, sender=User)
def notification_new_user(sender, instance: User, created: bool, **kwargs):
NEWLINE = "\r\n"
if not created:
return
# Create Notification text
subject = _("Neuer User") + f": {instance.username}"
new_user_text = _("Es hat sich eine neue Person registriert.") + f"{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 = new_user_text + user_detail_text + link_text
for moderator in User.objects.filter(trust_level__gt=TrustLevel.MODERATOR):
notification = BaseNotification.objects.create(title=subject, text=body_text, user=moderator)
notification.save()

View File

@ -95,7 +95,6 @@ textarea {
.container-cards {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.card {
@ -453,11 +452,13 @@ select, .button {
.card h1 {
color: var(--text-three);
text-shadow: 1px 1px var(--shadow-three);
width: 85%;
}
.card h2 {
color: var(--text-three);
text-shadow: 1px 1px var(--shadow-three);
width: 85%;
}
.card img {

View File

@ -1,7 +1,5 @@
from celery.app import shared_task
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.misc import healthcheck_ok
from .models import Location, AdoptionNotice, Timestamp
@ -45,8 +43,3 @@ def add_adoption_notice_location(pk):
def task_healthcheck():
healthcheck_ok()
set_timestamp("task_healthcheck")
@shared_task
def task_send_notification_email(notification_pk):
send_notification_email(notification_pk)

View File

@ -1,29 +0,0 @@
{% extends "fellchensammlung/base_generic.html" %}
{% load custom_tags %}
{% load i18n %}
{% block title %}<title>{{ org.name }}</title>{% endblock %}
{% block content %}
<div class="card">
<h1>{{ org.name }}</h1>
<b><i class="fa-solid fa-location-dot"></i></b>
{% if org.location %}
{{ org.location.str_hr }}
{% else %}
{{ org.location_string }}
{% endif %}
<p>{{ org.description | render_markdown }}</p>
</div>
<h2>{% translate 'Vermittlungen der Organisation' %}</h2>
<div class="container-cards">
{% if org.adoption_notices %}
{% for adoption_notice in org.adoption_notices %}
{% include "fellchensammlung/partials/partial-adoption-notice-minimal.html" %}
{% endfor %}
{% else %}
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
{% endif %}
</div>
{% endblock %}

View File

@ -13,38 +13,12 @@
<p>{% translate "Keine bevorzugte Sprache gesetzt." %}</p>
{% endif %}
<div class="container-cards">
{% if user.id is request.user.id %}
<div class="card">
{% if token %}
<form action="" method="POST">
{% csrf_token %}
<p class="text-muted"><strong>{% translate "API token:" %}</strong> {{ token }}</p>
<input class="btn" type="submit" name="delete_token"
value={% translate "Delete API token" %}>
</form>
{% else %}
<p>{% translate "Kein API-Token vorhanden." %}</p>
<form action="" method="POST">
{% csrf_token %}
<input class="btn" type="submit" name="create_token"
value={% translate "Create API token" %}>
</form>
{% endif %}
</div>
</div><p>
<div class="container-comment-form">
<h2>{% trans 'Profil verwalten' %}</h2>
<p>
<a class="btn2" href="{% url 'password_change' %}">{% translate "Change password" %}</a>
<a class="btn2" href="{% url 'user-me-export' %}">{% translate "Daten exportieren" %}</a>
</p>
</div>
</p>
<h2>{% translate 'Benachrichtigungen' %}</h2>
{% include "fellchensammlung/lists/list-notifications.html" %}
<h2>{% translate 'Meine Vermittlungen' %}</h2>
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
{% if user.id is request.user.id %}
{% endif %}
<h2>{% translate 'Benachrichtigungen' %}</h2>
{% include "fellchensammlung/lists/list-notifications.html" %}
<h2>{% translate 'Meine Vermittlungen' %}</h2>
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
{% endif %}
{% endblock %}

View File

@ -2,7 +2,7 @@
{% load custom_tags %}
{% load i18n %}
{% block title %}<title>{{ adoption_notice.name }}</title>{% endblock %}
{% block title %}<title>{{adoption_notice.name }}</title>{% endblock %}
{% block content %}
<div class="detail-adoption-notice-header">
@ -34,9 +34,6 @@
<table>
<tr>
<th>{% translate "Ort" %}</th>
{% if adoption_notice.organization %}
<th>{% translate "Organisation" %}</th>
{% endif %}
<th>{% translate "Suchen seit" %}</th>
<th>{% translate "Zuletzt aktualisiert" %}</th>
<th>{% translate "Weitere Informationen" %}</th>
@ -49,9 +46,6 @@
{{ adoption_notice.location_string }}
{% endif %}
</td>
{% if adoption_notice.organization %}
<td><a href="{{adoption_notice.organization.get_absolute_url }}">{{ adoption_notice.organization }}</a></td>
{% endif %}
<td>{{ adoption_notice.searching_since }}</td>
<td>{{ adoption_notice.last_checked | date:'d. F Y' }}</td>
@ -60,8 +54,7 @@
<form method="get" action="{% url 'external-site' %}">
<input type="hidden" name="url" value="{{ adoption_notice.further_information }}">
<button class="btn" type="submit" id="submit">
{{ adoption_notice.further_information | domain }} <i
class="fa-solid fa-arrow-up-right-from-square"></i>
{{ adoption_notice.further_information | domain }} <i class="fa-solid fa-arrow-up-right-from-square"></i>
</button>
</form>
</td>

View File

@ -1,15 +0,0 @@
{% extends "fellchensammlung/base_generic.html" %}
{% load i18n %}
{% load custom_tags %}
{% block title %}<title>{% translate "403 Forbidden" %}</title>{% endblock %}
{% block content %}
<h1>403 Forbidden</h1>
<p>
{% blocktranslate %}
Diese Aktion ist dir nicht erlaubt. Logge dich ein oder nutze einen anderen Account. Wenn du denkst, dass hier
ein Fehler vorliegt, kontaktiere das Team!
{% endblocktranslate %}
</p>
{% endblock %}

View File

@ -8,6 +8,9 @@
<option value="{{ language.0 }}" {% if language.0 == LANGUAGE_CODE_CURRENT %} selected{% endif %}>
{{ language.0|language_name_local }}
</option>
<!--<option value="{{ language.0 }}" {% if language.0 == LANGUAGE_CODE %} selected{% endif %}>
{{ language.0|language_name_local }} ({{ language.0 }})
</option>-->
{% endfor %}
</select>
<!--<input type="submit" value={% translate "change" %}>-->

View File

@ -26,7 +26,7 @@
</div>
<a class="btn2" href="{% url 'user-me' %}"><i aria-hidden="true" class="fas fa-user"></i></a>
<a class="btn2" href="{{ user.get_absolute_url }}"><i aria-hidden="true" class="fas fa-user"></i></a>
<form class="btn2 button_darken" action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button class="button" type="submit"><i aria-hidden="true" class="fas fa-sign-out"></i></button>

View File

@ -24,8 +24,6 @@ urlpatterns = [
path("vermittlung/<int:adoption_notice_id>/add-photo", views.add_photo_to_adoption_notice, name="adoption-notice-add-photo"),
# 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("organisation/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
name="rescue-organization-detail"),
# ex: /search/
path("suchen/", views.search, name="search"),
@ -52,9 +50,7 @@ urlpatterns = [
## USERS ##
###########
# 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/me/export/', views.export_own_profile, name='user-me-export'),
path("user/<int:user_id>/", views.user_detail, name="user-detail"),
path('accounts/register/',
RegistrationView.as_view(

View File

@ -1,14 +1,12 @@
import logging
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import render, redirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.utils import translation
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import user_passes_test
from django.core.serializers import serialize
import json
from .mail import mail_admins_new_report
from notfellchen import settings
@ -16,7 +14,7 @@ from notfellchen import settings
from fellchensammlung import logger
from .models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \
User, Location, AdoptionNoticeStatus, Subscriptions, CommentNotification, BaseNotification, RescueOrganization, \
Species, Log, Timestamp, TrustLevel
Species, Log, Timestamp
from .forms import AdoptionNoticeForm, AdoptionNoticeFormWithDateWidget, ImageForm, ReportAdoptionNoticeForm, \
CommentForm, ReportCommentForm, AnimalForm, \
AdoptionNoticeSearchForm, AnimalFormWithDateWidget, AdoptionNoticeFormWithDateWidgetAutoAnimal
@ -25,19 +23,18 @@ from .tools.geo import GeoAPI
from .tools.metrics import gather_metrics_data
from .tools.admin import clean_locations, get_unchecked_adoption_notices, deactivate_unchecked_adoption_notices
from .tasks import add_adoption_notice_location
from rest_framework.authtoken.models import Token
def user_is_trust_level_or_above(user, trust_level=TrustLevel.MODERATOR):
return user.is_authenticated and user.trust_level >= trust_level
def user_is_trust_level_or_above(user, trust_level=User.MODERATOR):
return user.is_authenticated and user.trust_level >= User.TRUST_LEVEL[trust_level]
def user_is_owner_or_trust_level(user, django_object, trust_level=TrustLevel.MODERATOR):
def user_is_owner_or_trust_level(user, django_object, trust_level=User.MODERATOR):
return user.is_authenticated and (
user.trust_level == trust_level or django_object.owner == user)
user.trust_level == User.TRUST_LEVEL[trust_level] or django_object.owner == user)
def fail_if_user_not_owner_or_trust_level(user, django_object, trust_level=TrustLevel.MODERATOR):
def fail_if_user_not_owner_or_trust_level(user, django_object, trust_level=User.MODERATOR):
if not user_is_owner_or_trust_level(user, django_object, trust_level):
raise PermissionDenied
@ -72,9 +69,7 @@ def change_language(request):
translation.activate(language_code)
response = HttpResponseRedirect(redirect_path)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language_code)
return response
else:
return render(request, 'fellchensammlung/index.html')
return response
def adoption_notice_detail(request, adoption_notice_id):
@ -153,8 +148,7 @@ def adoption_notice_edit(request, adoption_notice_id):
adoption_notice_instance.save()
"""Log"""
Log.objects.create(user=request.user, action="adoption_notice_edit",
text=f"{request.user} hat Vermittlung {adoption_notice.pk} geändert")
Log.objects.create(user=request.user, action="adoption_notice_edit", text=f"{request.user} hat Vermittlung {adoption_notice.pk} geändert")
return redirect(reverse("adoption-notice-detail", args=[adoption_notice_instance.pk], ))
else:
form = AdoptionNoticeForm(instance=adoption_notice)
@ -210,7 +204,7 @@ def add_adoption_notice(request):
add_adoption_notice_location.delay_on_commit(instance.pk)
# Set correct status
if request.user.trust_level >= TrustLevel.MODERATOR:
if request.user.trust_level >= User.TRUST_LEVEL[User.COORDINATOR]:
instance.set_active()
else:
instance.set_unchecked()
@ -420,41 +414,16 @@ def report_detail_success(request, report_id):
return report_detail(request, report_id, form_complete=True)
def user_detail(request, user, token=None):
context = {"user": user,
"adoption_notices": AdoptionNotice.objects.filter(owner=user),
"notifications": BaseNotification.objects.filter(user=user, read=False)}
if token is not None:
context["token"] = token
return render(request, 'fellchensammlung/details/detail-user.html', context=context)
@login_required
def user_by_id(request, user_id):
def user_detail(request, user_id):
user = User.objects.get(id=user_id)
# Only users that are mods or owners of the user are allowed to view
fail_if_user_not_owner_or_trust_level(request.user, user)
if user == request.user:
return my_profile(request)
else:
return user_detail(request, user)
@login_required()
def my_profile(request):
if request.method == 'POST':
if "create_token" in request.POST:
Token.objects.create(user=request.user)
elif "delete_token" in request.POST:
Token.objects.get(user=request.user).delete()
if request.method == "POST":
action = request.POST.get("action")
if action == "notification_mark_read":
notification_id = request.POST.get("notification_id")
try:
notification = CommentNotification.objects.get(pk=notification_id)
except CommentNotification.DoesNotExist:
notification = BaseNotification.objects.get(pk=notification_id)
notification = CommentNotification.objects.get(pk=notification_id)
notification.read = True
notification.save()
elif action == "notification_mark_all_read":
@ -462,11 +431,11 @@ def my_profile(request):
for notification in notifications:
notification.read = True
notification.save()
try:
token = Token.objects.get(user=request.user)
except Token.DoesNotExist:
token = None
return user_detail(request, request.user, token)
context = {"user": user,
"adoption_notices": AdoptionNotice.objects.filter(owner=user),
"notifications": CommentNotification.objects.filter(user=user, read=False)}
return render(request, 'fellchensammlung/details/detail-user.html', context=context)
@user_passes_test(user_is_trust_level_or_above)
@ -478,19 +447,16 @@ def modqueue(request):
@login_required
def updatequeue(request):
#TODO: Make sure update can only be done for instances with permission
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()
if user_is_trust_level_or_above(request.user, TrustLevel.MODERATOR):
if user_is_trust_level_or_above(request.user, User.MODERATOR):
last_checked_adoption_list = AdoptionNotice.objects.order_by("last_checked")
else:
last_checked_adoption_list = AdoptionNotice.objects.filter(owner=request.user).order_by("last_checked")
@ -575,20 +541,3 @@ def external_site_warning(request):
context.update(texts)
return render(request, 'fellchensammlung/external_site_warning.html', context=context)
def detail_view_rescue_organization(request, rescue_organization_id):
org = RescueOrganization.objects.get(pk=rescue_organization_id)
return render(request, 'fellchensammlung/details/detail-rescue-organization.html', context={"org": org})
def export_own_profile(request):
user = request.user
ANs = AdoptionNotice.objects.filter(owner=user)
user_as_json = serialize('json', [user])
user_editable = json.loads(user_as_json)
user_editable[0]["fields"]["password"] = "Password hash redacted for security reasons"
user_as_json = json.dumps(user_editable)
ANs_as_json = serialize('json', ANs)
full_json = f"{user_as_json}, {ANs_as_json}"
return HttpResponse(full_json, content_type="application/json")

View File

@ -169,7 +169,6 @@ INSTALLED_APPS = [
'crispy_forms',
"crispy_bootstrap4",
"rest_framework",
'rest_framework.authtoken'
]
MIDDLEWARE = [

View File

@ -25,9 +25,9 @@ urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += i18n_patterns(
urlpatterns += i18n_patterns (
path("", include("fellchensammlung.urls")),
prefix_default_language=False
prefix_default_language = False
)
if settings.DEBUG:

View File

@ -1,10 +1,8 @@
{% load i18n %}
{{ site.name }}: {% trans "Account aktivieren" %}
{% trans "Account aktivieren" %} {{ site.name }}:
{% trans 'Hier ist dein Aktivierungs-Key. Mit diesem kannst du deinen Account freischalten.' %}
{{ activation_key }}
<a href="{{ site.domain }}{% url 'django_registration_activate' activation_key%}">{% trans "Activate by clicking this link" %}</a>
{% trans "oder öffne den folgenden link im Browser" %}:
{{ site.domain }}{% url 'django_registration_activate' activation_key%}
{% trans "Öffne den folgenden link im Browser und gib den Aktivierungs-Key dort ein" %}:
https://{{ site.domain }}{% url 'django_registration_activate' %}
{% blocktrans %}Der Link ist für {{ expiration_days }} Tage gültig.{% endblocktrans %}
{% blocktrans %}Der link ist gültig für {{ expiration_days }} tage.{% endblocktrans %}

View File

@ -1 +1 @@
{% load i18n %}{{ site.name }}: {% translate "Account aktivieren" %}
{% load i18n %}{% translate "Account aktivieren" %} {{ site.name }}

View File

@ -1,15 +0,0 @@
{% extends "fellchensammlung/base_generic.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block content %}
{% if not user.is_authenticated %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="btn2" value={% translate 'Absenden' %}>
</form>
{% else %}
<p>{% translate "Du bist bereits eingeloggt." %}</p>
{% endif %}
{% endblock %}

View File

@ -2,5 +2,5 @@
{% load i18n %}
{% block content %}
<p>{% translate "Du bist nun registriert. Du hast eine E-Mail mit einem Link zur Aktivierung deines Kontos bekommen." %}</p>
<p>{% translate "Du bist nun registriert. Du hast eine E-Mail mit einem Link zur aktivierung bekommen." %}</p>
{% endblock %}

View File

@ -4,7 +4,7 @@ from django.utils import timezone
from django.test import TestCase
from model_bakery import baker
from fellchensammlung.models import Announcement, Language, User, TrustLevel
from fellchensammlung.models import Announcement, Language, User
class UserTest(TestCase):
@ -12,7 +12,7 @@ class UserTest(TestCase):
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 == TrustLevel.MEMBER)
self.assertTrue(test_user_1.trust_level == User.TRUST_LEVEL[User.MEMBER])
class AnnouncementTest(TestCase):

View File

@ -4,7 +4,7 @@ from django.urls import reverse
from model_bakery import baker
from fellchensammlung.models import Animal, Species, AdoptionNotice, User, Location, AdoptionNoticeStatus, TrustLevel
from fellchensammlung.models import Animal, Species, AdoptionNotice, User, Location, AdoptionNoticeStatus
from fellchensammlung.views import add_adoption_notice
@ -20,7 +20,7 @@ class AnimalAndAdoptionTest(TestCase):
first_name="Max",
last_name="Müller",
password='12345')
test_user0.trust_level = TrustLevel.ADMIN
test_user0.trust_level = User.TRUST_LEVEL[User.ADMIN]
test_user0.save()
adoption1 = baker.make(AdoptionNotice, name="TestAdoption1")
@ -133,7 +133,7 @@ class UpdateQueueTest(TestCase):
first_name="Admin",
last_name="BOFH",
password='12345',
trust_level=TrustLevel.MODERATOR)
trust_level=User.TRUST_LEVEL[User.MODERATOR])
test_user0.is_superuser = True
test_user0.save()