Compare commits

..

37 Commits

Author SHA1 Message Date
3eb7dbe984 fix: Allow all notifications to be marked as read
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-20 23:37:55 +01:00
202dfe46c2 fix: For some reason this needs width now? 2024-11-20 23:34:25 +01:00
01da0f1e29 feat: add 403 page 2024-11-20 23:23:06 +01:00
8ccdf50bc5 refactor: Use new trust level class 2024-11-20 23:08:02 +01:00
d46ab8da6b feat: Show all notifications on profile 2024-11-20 23:03:02 +01:00
1dd53a87e9 feat: Notify admins of new user via notification framework #10 2024-11-20 23:02:41 +01:00
40bb2e54bd fix: Readjust trust level 2024-11-20 23:01:19 +01:00
433ad9d4b9 refactor: remove unnecessary print 2024-11-20 22:53:48 +01:00
231c27819d feat: Send e-mail notifications to user 2024-11-20 20:26:44 +01:00
890309564f feat: Add field for user to opt-out of e-mail notifications 2024-11-20 20:26:05 +01:00
e1e1f822c8 fix: Use correct view 2024-11-20 20:00:36 +01:00
7a788f4c90 fix: Allow users to perform actions on own profile 2024-11-20 20:00:27 +01:00
7efa626b8b feat: Migrate to new Integer choice field to allow nicer handling 2024-11-20 19:55:16 +01:00
08e20e1875 feat: Add basic user data export 2024-11-18 23:01:27 +01:00
f1c79a5f94 feat: UI improvements for user profile 2024-11-18 22:58:32 +01:00
5dd1991af8 feat: Restructure view of own profile, add token authorization for API 2024-11-18 22:41:12 +01:00
c0edef51bd feat: add explanation for signup reason 2024-11-18 18:34:12 +01:00
cb703e79ae feat: add fancy rat 2024-11-18 18:33:53 +01:00
87066b0cea feat: add signup-reason 2024-11-14 21:54:32 +01:00
c4976c4b34 feat: Upgrade django-registration to 5.1 2024-11-14 21:54:17 +01:00
ee46ff9cda fix: typo 2024-11-14 21:53:24 +01:00
d4f27e8f2f feat: Allow to set organization when creating adoption notice 2024-11-14 21:11:34 +01:00
4a6584370e feat: re-add understrike to buttons 2024-11-14 21:11:12 +01:00
82d3f95c99 feat: add understrike to improve accessibility 2024-11-14 21:00:52 +01:00
dce3d89c7e feat: rename comment to internal comment 2024-11-14 19:31:42 +01:00
5520590145 feat: Add link to rescue org 2024-11-14 19:29:41 +01:00
efabebfdbf feat: Add ANs to rescue organization 2024-11-14 19:27:32 +01:00
6c52246bb7 feat: Add detail view for organizations 2024-11-14 19:16:47 +01:00
2c11f7c385 feat: Add description for organizations 2024-11-14 19:01:24 +01:00
9ee0bd8e30 feat: Add rescue to detail view 2024-11-14 18:49:28 +01:00
1955476d24 feat: Improve activation e-mail format 2024-11-14 18:32:08 +01:00
05178da029 feat: Add captcha to registration 2024-11-14 18:30:51 +01:00
7a80cf8df1 refactor: remove unused 2024-11-14 18:29:56 +01:00
db94ec41ed feat: Add organization affiliation to user 2024-11-14 18:28:55 +01:00
5582538a70 fix: Pin django registration version, otherwise causes reverse error 2024-11-14 18:28:02 +01:00
7aa364fc38 refactor: remove unnecessary print 2024-11-14 07:10:49 +01:00
96ce5963fe feat: add phone number 2024-11-14 07:10:27 +01:00
33 changed files with 518 additions and 165 deletions

View File

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

View File

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

View File

@ -9,6 +9,14 @@ from django.utils.translation import gettext_lazy as _
from notfellchen.settings import MEDIA_URL 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): class DateInput(forms.DateInput):
input_type = 'date' input_type = 'date'
@ -43,6 +51,7 @@ class AdoptionNoticeForm(forms.ModelForm):
'group_only', 'group_only',
'searching_since', 'searching_since',
'location_string', 'location_string',
'organization',
'description', 'description',
'further_information', 'further_information',
), ),
@ -50,19 +59,19 @@ class AdoptionNoticeForm(forms.ModelForm):
class Meta: class Meta:
model = AdoptionNotice model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"] fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
"organization"]
class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm): class AdoptionNoticeFormWithDateWidget(AdoptionNoticeForm):
class Meta: class Meta:
model = AdoptionNotice model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"] fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string", "organization"]
widgets = { widgets = {
'searching_since': DateInput(), 'searching_since': DateInput(),
} }
class AnimalForm(forms.ModelForm): class AnimalForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'in_adoption_notice_creation_flow' in kwargs: if 'in_adoption_notice_creation_flow' in kwargs:
@ -91,6 +100,7 @@ class AnimalFormWithDateWidget(AnimalForm):
'date_of_birth': DateInput(), 'date_of_birth': DateInput(),
} }
class AdoptionNoticeFormWithDateWidgetAutoAnimal(AdoptionNoticeFormWithDateWidget): class AdoptionNoticeFormWithDateWidgetAutoAnimal(AdoptionNoticeFormWithDateWidget):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AdoptionNoticeFormWithDateWidgetAutoAnimal, self).__init__(*args, **kwargs) super(AdoptionNoticeFormWithDateWidgetAutoAnimal, self).__init__(*args, **kwargs)
@ -159,6 +169,8 @@ class CustomRegistrationForm(RegistrationForm):
class Meta(RegistrationForm.Meta): class Meta(RegistrationForm.Meta):
model = User 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.helper = FormHelper() self.helper = FormHelper()
@ -169,7 +181,7 @@ class CustomRegistrationForm(RegistrationForm):
def _get_distances(): def _get_distances():
return {i: i for i in [10, 20, 50, 100, 200, 500]} return {i: i for i in [20, 50, 100, 200, 500]}
class AdoptionNoticeSearchForm(forms.Form): class AdoptionNoticeSearchForm(forms.Form):

View File

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

View File

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

View File

@ -0,0 +1,18 @@
# 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

@ -0,0 +1,19 @@
# 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

@ -0,0 +1,18 @@
# 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

@ -0,0 +1,18 @@
# 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

@ -0,0 +1,18 @@
# 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

@ -0,0 +1,19 @@
# 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

@ -0,0 +1,23 @@
# 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

@ -0,0 +1,18 @@
# 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,86 +34,6 @@ class Language(models.Model):
verbose_name_plural = _('Sprachen') 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): class Location(models.Model):
place_id = models.IntegerField() place_id = models.IntegerField()
latitude = models.FloatField() latitude = models.FloatField()
@ -186,10 +106,104 @@ class RescueOrganization(models.Model):
facebook = models.URLField(null=True, blank=True, verbose_name=_('Facebook Profil')) facebook = models.URLField(null=True, blank=True, verbose_name=_('Facebook Profil'))
fediverse_profile = models.URLField(null=True, blank=True, verbose_name=_('Fediverse Profil')) fediverse_profile = models.URLField(null=True, blank=True, verbose_name=_('Fediverse Profil'))
email = models.EmailField(null=True, blank=True, verbose_name=_('E-Mail')) 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')) website = models.URLField(null=True, blank=True, verbose_name=_('Website'))
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
comment = models.TextField(verbose_name=_("Kommentar"), null=True, blank=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')
class AdoptionNotice(models.Model): class AdoptionNotice(models.Model):
@ -239,7 +253,6 @@ class AdoptionNotice(models.Model):
else: else:
return "mixed" return "mixed"
@property @property
def comments(self): def comments(self):
return Comment.objects.filter(adoption_notice=self) return Comment.objects.filter(adoption_notice=self)
@ -715,7 +728,6 @@ class CommentNotification(BaseNotification):
@property @property
def url(self): def url(self):
print(f"URL: self.comment.get_absolute_url()")
return self.comment.get_absolute_url return self.comment.get_absolute_url

View File

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

View File

@ -1,5 +1,7 @@
from celery.app import shared_task
from django.utils import timezone from django.utils import timezone
from notfellchen.celery import app as celery_app 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.admin import clean_locations, deactivate_unchecked_adoption_notices, deactivate_404_adoption_notices
from .tools.misc import healthcheck_ok from .tools.misc import healthcheck_ok
from .models import Location, AdoptionNotice, Timestamp from .models import Location, AdoptionNotice, Timestamp
@ -43,3 +45,8 @@ def add_adoption_notice_location(pk):
def task_healthcheck(): def task_healthcheck():
healthcheck_ok() healthcheck_ok()
set_timestamp("task_healthcheck") set_timestamp("task_healthcheck")
@shared_task
def task_send_notification_email(notification_pk):
send_notification_email(notification_pk)

View File

@ -0,0 +1,29 @@
{% 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,8 +13,34 @@
<p>{% translate "Keine bevorzugte Sprache gesetzt." %}</p> <p>{% translate "Keine bevorzugte Sprache gesetzt." %}</p>
{% endif %} {% endif %}
<div class="container-cards">
{% if user.id is request.user.id %} {% 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> <h2>{% translate 'Benachrichtigungen' %}</h2>
{% include "fellchensammlung/lists/list-notifications.html" %} {% include "fellchensammlung/lists/list-notifications.html" %}
<h2>{% translate 'Meine Vermittlungen' %}</h2> <h2>{% translate 'Meine Vermittlungen' %}</h2>

View File

@ -34,6 +34,9 @@
<table> <table>
<tr> <tr>
<th>{% translate "Ort" %}</th> <th>{% translate "Ort" %}</th>
{% if adoption_notice.organization %}
<th>{% translate "Organisation" %}</th>
{% endif %}
<th>{% translate "Suchen seit" %}</th> <th>{% translate "Suchen seit" %}</th>
<th>{% translate "Zuletzt aktualisiert" %}</th> <th>{% translate "Zuletzt aktualisiert" %}</th>
<th>{% translate "Weitere Informationen" %}</th> <th>{% translate "Weitere Informationen" %}</th>
@ -46,6 +49,9 @@
{{ adoption_notice.location_string }} {{ adoption_notice.location_string }}
{% endif %} {% endif %}
</td> </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.searching_since }}</td>
<td>{{ adoption_notice.last_checked | date:'d. F Y' }}</td> <td>{{ adoption_notice.last_checked | date:'d. F Y' }}</td>
@ -54,7 +60,8 @@
<form method="get" action="{% url 'external-site' %}"> <form method="get" action="{% url 'external-site' %}">
<input type="hidden" name="url" value="{{ adoption_notice.further_information }}"> <input type="hidden" name="url" value="{{ adoption_notice.further_information }}">
<button class="btn" type="submit" id="submit"> <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> </button>
</form> </form>
</td> </td>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,15 @@
{% 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 %} {% load i18n %}
{% block content %} {% block content %}
<p>{% translate "Du bist nun registriert. Du hast eine E-Mail mit einem Link zur aktivierung bekommen." %}</p> <p>{% translate "Du bist nun registriert. Du hast eine E-Mail mit einem Link zur Aktivierung deines Kontos bekommen." %}</p>
{% endblock %} {% endblock %}

View File

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

View File

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