Compare commits
	
		
			22 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 151ce0d88e | |||
| e07e633651 | |||
| dd3b1fde9d | |||
| 2ffc9b4ba1 | |||
| 22eebd4586 | |||
| e589a048d3 | |||
| 392eb5a7a8 | |||
| 44fa4d4880 | |||
| 9b97cc4cb1 | |||
| 656a24ef02 | |||
| 74643db087 | |||
| 3a6fd3cee1 | |||
| 29e9d1bd8c | |||
| 3c5ca9ae00 | |||
| 3d1ad6112d | |||
| b843e67e9b | |||
| 4cab71e8fb | |||
| 969339a95f | |||
| e06efa1539 | |||
| 2fb6d2782f | |||
| f69eccd0e4 | |||
| e20e6d4b1d | 
@@ -8,6 +8,7 @@ host=localhost
 | 
			
		||||
[django]
 | 
			
		||||
secret=CHANGE-ME
 | 
			
		||||
debug=True
 | 
			
		||||
internal_ips=["127.0.0.1"]
 | 
			
		||||
 | 
			
		||||
[database]
 | 
			
		||||
backend=sqlite3
 | 
			
		||||
@@ -28,3 +29,6 @@ django_log_level=INFO
 | 
			
		||||
api_url=https://photon.hyteck.de/api
 | 
			
		||||
api_format=photon
 | 
			
		||||
 | 
			
		||||
[security]
 | 
			
		||||
totp_issuer="NF Localhost"
 | 
			
		||||
webauth_allow_insecure_origin=True
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,8 @@ dependencies = [
 | 
			
		||||
    "celery[redis]",
 | 
			
		||||
    "drf-spectacular[sidecar]",
 | 
			
		||||
    "django-widget-tweaks",
 | 
			
		||||
    "django-super-deduper"
 | 
			
		||||
    "django-super-deduper",
 | 
			
		||||
    "django-allauth[mfa]",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
dynamic = ["version", "readme"]
 | 
			
		||||
@@ -48,6 +49,7 @@ develop = [
 | 
			
		||||
    "pytest",
 | 
			
		||||
    "coverage",
 | 
			
		||||
    "model_bakery",
 | 
			
		||||
    "debug_toolbar",
 | 
			
		||||
]
 | 
			
		||||
docs = [
 | 
			
		||||
    "sphinx",
 | 
			
		||||
 
 | 
			
		||||
@@ -164,6 +164,13 @@ class SocialMediaPostAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_filter = ("platform",)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Log)
 | 
			
		||||
class LogAdmin(admin.ModelAdmin):
 | 
			
		||||
    ordering = ["-created_at"]
 | 
			
		||||
    list_filter = ("action",)
 | 
			
		||||
    list_display = ("action", "user", "created_at")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
admin.site.register(Animal)
 | 
			
		||||
admin.site.register(Species)
 | 
			
		||||
admin.site.register(Rule)
 | 
			
		||||
@@ -172,5 +179,4 @@ admin.site.register(ModerationAction)
 | 
			
		||||
admin.site.register(Language)
 | 
			
		||||
admin.site.register(Announcement)
 | 
			
		||||
admin.site.register(Subscriptions)
 | 
			
		||||
admin.site.register(Log)
 | 
			
		||||
admin.site.register(Timestamp)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.forms.widgets import Textarea
 | 
			
		||||
 | 
			
		||||
from .models import AdoptionNotice, Animal, Image, ReportAdoptionNotice, ReportComment, ModerationAction, User, Species, \
 | 
			
		||||
    Comment, SexChoicesWithAll, DistanceChoices, SpeciesSpecificURL, RescueOrganization
 | 
			
		||||
@@ -9,6 +10,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from notfellchen.settings import MEDIA_URL
 | 
			
		||||
from crispy_forms.layout import Div
 | 
			
		||||
 | 
			
		||||
from .tools.model_helpers import reason_for_signup_label, reason_for_signup_help_text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def animal_validator(value: str):
 | 
			
		||||
    value = value.lower()
 | 
			
		||||
@@ -137,6 +140,18 @@ class ModerationActionForm(forms.ModelForm):
 | 
			
		||||
        fields = ('action', 'public_comment', 'private_comment')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddedRegistrationForm(forms.Form):
 | 
			
		||||
    reason_for_signup = forms.CharField(label=reason_for_signup_label,
 | 
			
		||||
                                        help_text=reason_for_signup_help_text,
 | 
			
		||||
                                        widget=Textarea)
 | 
			
		||||
    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 signup(self, request, user):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomRegistrationForm(RegistrationForm):
 | 
			
		||||
    class Meta(RegistrationForm.Meta):
 | 
			
		||||
        model = User
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ from notfellchen.settings import MEDIA_URL, base_url
 | 
			
		||||
from .tools.geo import LocationProxy, Position
 | 
			
		||||
from .tools.misc import time_since_as_hr_string
 | 
			
		||||
from .tools.model_helpers import NotificationTypeChoices, AdoptionNoticeStatusChoices, AdoptionProcess, \
 | 
			
		||||
    AdoptionNoticeStatusChoicesDescriptions, RegularCheckStatusChoices
 | 
			
		||||
    AdoptionNoticeStatusChoicesDescriptions, RegularCheckStatusChoices, reason_for_signup_label, \
 | 
			
		||||
    reason_for_signup_help_text
 | 
			
		||||
from .tools.model_helpers import ndm as NotificationDisplayMapping
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -267,10 +268,6 @@ class RescueOrganization(models.Model):
 | 
			
		||||
        """
 | 
			
		||||
        return self.instagram or self.facebook or self.website or self.phone_number or self.email or self.fediverse_profile
 | 
			
		||||
 | 
			
		||||
    def set_exclusion_from_checks(self):
 | 
			
		||||
        self.exclude_from_check = True
 | 
			
		||||
        self.save()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def child_organizations(self):
 | 
			
		||||
        return RescueOrganization.objects.filter(parent_org=self)
 | 
			
		||||
@@ -311,8 +308,7 @@ class User(AbstractUser):
 | 
			
		||||
    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."))
 | 
			
		||||
    reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text)
 | 
			
		||||
    email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
 | 
			
		||||
    REQUIRED_FIELDS = ["reason_for_signup", "email"]
 | 
			
		||||
 | 
			
		||||
@@ -423,9 +419,10 @@ class AdoptionNotice(models.Model):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def num_per_sex(self):
 | 
			
		||||
        print(f"{self.pk} x")
 | 
			
		||||
        num_per_sex = dict()
 | 
			
		||||
        for sex in SexChoices:
 | 
			
		||||
            num_per_sex[sex] = self.animals.filter(sex=sex).count()
 | 
			
		||||
            num_per_sex[sex] = len([animal for animal in self.animals if animal.sex == sex])
 | 
			
		||||
        return num_per_sex
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@@ -515,6 +512,7 @@ class AdoptionNotice(models.Model):
 | 
			
		||||
                photos.extend(animal.photos.all())
 | 
			
		||||
            if len(photos) > 0:
 | 
			
		||||
                return photos
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_photo(self):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/fellchensammlung/templates/allauth/elements/badge.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/fellchensammlung/templates/allauth/elements/badge.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
{% setvar variant %}
 | 
			
		||||
    {% if "primary" in attrs.tags %}
 | 
			
		||||
        is-success
 | 
			
		||||
    {% elif "secondary" in attrs.tags %}
 | 
			
		||||
        is-success is-light
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endsetvar %}
 | 
			
		||||
<span class="tag{% if variant %} {{ variant }}{% endif %}" {% if attrs.title %}title="{{ attrs.title }}"{% endif %}>
 | 
			
		||||
    {% slot %}
 | 
			
		||||
    {% endslot %}
 | 
			
		||||
</span>
 | 
			
		||||
							
								
								
									
										15
									
								
								src/fellchensammlung/templates/allauth/elements/button.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/fellchensammlung/templates/allauth/elements/button.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
{% comment %} djlint:off {% endcomment %}
 | 
			
		||||
<div class="control">
 | 
			
		||||
    <{% if attrs.href %}a href="{{ attrs.href }}"{% else %}button{% endif %}
 | 
			
		||||
    class="button is-primary"
 | 
			
		||||
    {% if attrs.form %}form="{{ attrs.form }}"{% endif %}
 | 
			
		||||
    {% if attrs.id %}id="{{ attrs.id }}"{% endif %}
 | 
			
		||||
    {% if attrs.name %}name="{{ attrs.name }}"{% endif %}
 | 
			
		||||
    {% if attrs.value %}value="{{ attrs.value }}"{% endif %}
 | 
			
		||||
    {% if attrs.type %}type="{{ attrs.type }}"{% endif %}
 | 
			
		||||
    >
 | 
			
		||||
    {% slot %}
 | 
			
		||||
    {% endslot %}
 | 
			
		||||
    </{% if attrs.href %}a{% else %}button{% endif %}>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
<div class="field is-grouped">
 | 
			
		||||
    {% slot %}
 | 
			
		||||
    {% endslot %}
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										50
									
								
								src/fellchensammlung/templates/allauth/elements/field.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/fellchensammlung/templates/allauth/elements/field.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
<div class="field">
 | 
			
		||||
 | 
			
		||||
    {% if attrs.type == "textarea" %}
 | 
			
		||||
        <label class="label" for="{{ attrs.id }}">
 | 
			
		||||
            {% slot label %}
 | 
			
		||||
            {% endslot %}
 | 
			
		||||
        </label>
 | 
			
		||||
        <textarea class="textarea"
 | 
			
		||||
                  {% if attrs.required %}required{% endif %}
 | 
			
		||||
                  {% if attrs.rows %}rows="{{ attrs.rows }}"{% endif %}
 | 
			
		||||
                  {% if attrs.disabled %}disabled{% endif %}
 | 
			
		||||
                  {% if attrs.readonly %}readonly{% endif %}
 | 
			
		||||
                  {% if attrs.checked %}checked{% endif %}
 | 
			
		||||
                  {% if attrs.name %}name="{{ attrs.name }}"{% endif %}
 | 
			
		||||
                  {% if attrs.id %}id="{{ attrs.id }}"{% endif %}
 | 
			
		||||
                  {% if attrs.placeholder %}placeholder="{{ attrs.placeholder }}"{% endif %}>{% slot value %}{% endslot %}</textarea>
 | 
			
		||||
    {% else %}
 | 
			
		||||
        {% if attrs.type != "checkbox" and attrs.type != "radio" %}
 | 
			
		||||
            <label class="label" for="{{ attrs.id }}">
 | 
			
		||||
                {% slot label %}
 | 
			
		||||
                {% endslot %}
 | 
			
		||||
            </label>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <input {% if attrs.type != "checkbox" and attrs.type != "radio" %}class="input"{% endif %}
 | 
			
		||||
               {% if attrs.required %}required{% endif %}
 | 
			
		||||
               {% if attrs.disabled %}disabled{% endif %}
 | 
			
		||||
               {% if attrs.readonly %}readonly{% endif %}
 | 
			
		||||
               {% if attrs.checked %}checked{% endif %}
 | 
			
		||||
               {% if attrs.name %}name="{{ attrs.name }}"{% endif %}
 | 
			
		||||
               {% if attrs.id %}id="{{ attrs.id }}"{% endif %}
 | 
			
		||||
               {% if attrs.placeholder %}placeholder="{{ attrs.placeholder }}"{% endif %}
 | 
			
		||||
               {% if attrs.autocomplete %}autocomplete="{{ attrs.autocomplete }}"{% endif %}
 | 
			
		||||
               {% if attrs.value is not None %}value="{{ attrs.value }}"{% endif %}
 | 
			
		||||
               type="{{ attrs.type }}">
 | 
			
		||||
        {% if attrs.type == "checkbox" or attrs.type == "radio" %}
 | 
			
		||||
            <label for="{{ attrs.id }}">
 | 
			
		||||
                {% slot label %}
 | 
			
		||||
                {% endslot %}
 | 
			
		||||
            </label>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% if slots.help_text %}
 | 
			
		||||
        <p class="help is-danger">
 | 
			
		||||
            {% slot help_text %}
 | 
			
		||||
            {% endslot %}
 | 
			
		||||
        </p>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <p class="help is-danger">{{ attrs.errors }}</p>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
{{ attrs.form }}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/fellchensammlung/templates/allauth/elements/form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/fellchensammlung/templates/allauth/elements/form.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
<div class="block">
 | 
			
		||||
    <form method="{{ attrs.method }}"
 | 
			
		||||
          {% if attrs.action %}action="{{ attrs.action }}"{% endif %}>
 | 
			
		||||
        {% slot body %}
 | 
			
		||||
        {% endslot %}
 | 
			
		||||
        <div class="field is-grouped is-grouped-multiline">
 | 
			
		||||
            {% slot actions %}
 | 
			
		||||
            {% endslot %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										1
									
								
								src/fellchensammlung/templates/allauth/elements/h1.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/fellchensammlung/templates/allauth/elements/h1.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{% comment %} djlint:off {% endcomment %}{% load allauth %}<h1 class="title is-1">{% slot %}{% endslot %}</h1>
 | 
			
		||||
							
								
								
									
										1
									
								
								src/fellchensammlung/templates/allauth/elements/h2.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/fellchensammlung/templates/allauth/elements/h2.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{% comment %} djlint:off {% endcomment %}{% load allauth %}<h2 class="title is-2">{% slot %}{% endslot %}</h2>
 | 
			
		||||
							
								
								
									
										1
									
								
								src/fellchensammlung/templates/allauth/elements/p.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/fellchensammlung/templates/allauth/elements/p.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{% comment %} djlint:off {% endcomment %}{% load allauth %}<p class="content">{% slot %}{% endslot %}</p>
 | 
			
		||||
							
								
								
									
										18
									
								
								src/fellchensammlung/templates/allauth/elements/panel.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/fellchensammlung/templates/allauth/elements/panel.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
<section class="block">
 | 
			
		||||
    <h2 class="title is-2">
 | 
			
		||||
        {% slot title %}
 | 
			
		||||
        {% endslot %}
 | 
			
		||||
    </h2>
 | 
			
		||||
    {% slot body %}
 | 
			
		||||
    {% endslot %}
 | 
			
		||||
    {% if slots.actions %}
 | 
			
		||||
        <div class="field is-grouped is-grouped-multiline">
 | 
			
		||||
            {% for action in slots.actions %}
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    {{ action }}
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</section>
 | 
			
		||||
							
								
								
									
										1
									
								
								src/fellchensammlung/templates/allauth/layouts/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/fellchensammlung/templates/allauth/layouts/base.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{% extends "fellchensammlung/base.html" %}
 | 
			
		||||
@@ -37,6 +37,26 @@
 | 
			
		||||
{% block header %}
 | 
			
		||||
    {% include "fellchensammlung/header.html" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% if profile %}
 | 
			
		||||
    <div class="profile">
 | 
			
		||||
        <table class="table is-bordered is-fullwidth is-hoverable is-striped">
 | 
			
		||||
            <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>Timestamp</td>
 | 
			
		||||
                <td>Status</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
            {% for status in profile %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>{{ status.0 }}</td>
 | 
			
		||||
                    <td>{{ status.1 }}</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
<div class="main-content">
 | 
			
		||||
    {% block content %}{% endblock %}
 | 
			
		||||
</div>
 | 
			
		||||
@@ -45,5 +65,8 @@
 | 
			
		||||
{% block footer %}
 | 
			
		||||
    {% include "fellchensammlung/footer.html" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_body %}
 | 
			
		||||
{% endblock extra_body %}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,53 +19,10 @@
 | 
			
		||||
    <div class="columns">
 | 
			
		||||
        <div class="column">
 | 
			
		||||
            <div class="block">
 | 
			
		||||
                <div class="card">
 | 
			
		||||
                    <div class="card-header">
 | 
			
		||||
                        <h1 class="card-header-title">{{ org.name }}</h1>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="card-content">
 | 
			
		||||
                        <div class="block">
 | 
			
		||||
                            <b><i class="fa-solid fa-location-dot"></i></b>
 | 
			
		||||
                            {% if org.location %}
 | 
			
		||||
                                {{ org.location }}
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                                {{ org.location_string }}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if org.description %}
 | 
			
		||||
                                <div class="block content">
 | 
			
		||||
                                    <p>{{ org.description | render_markdown }}</p>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% 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>
 | 
			
		||||
                {% include "fellchensammlung/partials/rescue_orgs/partial-basic-info-card.html" %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="block">
 | 
			
		||||
                {% include "fellchensammlung/partials/partial-rescue-organization-contact.html" %}
 | 
			
		||||
                {% include "fellchensammlung/partials/rescue_orgs/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 %}">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
{% extends "fellchensammlung/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load account %}
 | 
			
		||||
 | 
			
		||||
{% block title %}<title>{{ user.get_full_name }}</title>{% endblock %}
 | 
			
		||||
{% block title %}<title>{% user_display user %}</title>{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
@@ -13,7 +14,7 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="level-right">
 | 
			
		||||
            <div class="level-item">
 | 
			
		||||
                <form class="" action="{% url 'logout' %}" method="post">
 | 
			
		||||
                <form class="" action="{% url 'account_logout' %}" method="post">
 | 
			
		||||
                    {% csrf_token %}
 | 
			
		||||
                    <button class="button" type="submit">
 | 
			
		||||
                        <i aria-hidden="true" class="fas fa-sign-out fa-fw"></i> Logout
 | 
			
		||||
@@ -25,12 +26,30 @@
 | 
			
		||||
 | 
			
		||||
    <div class="block">
 | 
			
		||||
        <h2 class="title is-2">{% trans 'Profil verwalten' %}</h2>
 | 
			
		||||
        <p><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</p>
 | 
			
		||||
        <div class="">
 | 
			
		||||
            <p>
 | 
			
		||||
                <a class="button is-warning" href="{% url 'password_change' %}">{% translate "Change password" %}</a>
 | 
			
		||||
                <a class="button is-info" href="{% url 'user-me-export' %}">{% translate "Daten exportieren" %}</a>
 | 
			
		||||
            </p>
 | 
			
		||||
        <div class="block"><strong>{% translate "E-Mail" %}:</strong> {{ user.email }}</div>
 | 
			
		||||
        <div class="block">
 | 
			
		||||
            <div class="field is-grouped is-grouped-multiline">
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    <a class="button is-warning"
 | 
			
		||||
                       href="{% url 'account_change_password' %}">{% translate "Passwort ändern" %}</a>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    <a class="button is-warning"
 | 
			
		||||
                       href="{% url 'account_email' %}">
 | 
			
		||||
                        {% translate "E-Mail Adresse ändern" %}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    <a class="button is-warning"
 | 
			
		||||
                       href="{% url 'mfa_index' %}">
 | 
			
		||||
                        {% translate "2-Faktor Authentifizierung" %}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="control">
 | 
			
		||||
                    <a class="button is-info" href="{% url 'user-me-export' %}">
 | 
			
		||||
                        {% translate "Daten exportieren" %}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load custom_tags %}
 | 
			
		||||
 | 
			
		||||
{% block title %}<title>{% translate "403 Forbidden" %}</title>{% endblock %}
 | 
			
		||||
{% block title %}<title>{% translate "404 Forbidden" %}</title>{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1 class="title is-1">404 Not Found</h1>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,35 @@
 | 
			
		||||
{% extends "fellchensammlung/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load widget_tweaks %}
 | 
			
		||||
{% load admin_urls %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
    <title>Organisation {{ rescue_org }} von regelmäßiger Prüfung ausschließen</title>
 | 
			
		||||
    <title>Organisation {{ org }} von regelmäßiger Prüfung ausschließen</title>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1 class="title is-1">Organisation {{ rescue_org }} von regelmäßiger Prüfung ausschließen</h1>
 | 
			
		||||
    <h1 class="title is-1">Organisation {{ org }} von regelmäßiger Prüfung ausschließen</h1>
 | 
			
		||||
    <div class="columns block">
 | 
			
		||||
        <div class="column">
 | 
			
		||||
            {% include "fellchensammlung/partials/rescue_orgs/partial-basic-info-card.html" %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="column">
 | 
			
		||||
            {% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization-contact.html" %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="block">
 | 
			
		||||
        <form method="post">
 | 
			
		||||
            {% csrf_token %}
 | 
			
		||||
            {{ form }}
 | 
			
		||||
 | 
			
		||||
        <a class="button" href="{% url 'organization-check' %}">{% translate "Zurück (nicht exkludieren)" %}</a>
 | 
			
		||||
        <input class="button is-danger" type="submit" name="delete" value="{% translate "Von regelmäßiger Prüfung ausschließen" %}">
 | 
			
		||||
            <input class="button is-primary" type="submit" name="delete"
 | 
			
		||||
                   value="{% translate "Aktualisieren" %}">
 | 
			
		||||
            <a class="button" href="{% url 'organization-check' %}">{% translate "Zurück" %}</a>
 | 
			
		||||
        </form>
 | 
			
		||||
    </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>
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
                {{ field|add_class:"input" }}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="help">
 | 
			
		||||
        <div class="help content">
 | 
			
		||||
            {{ field.help_text }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="help is-danger">
 | 
			
		||||
 
 | 
			
		||||
@@ -49,10 +49,10 @@
 | 
			
		||||
            {% else %}
 | 
			
		||||
                <div class="navbar-item">
 | 
			
		||||
                    <div class="buttons">
 | 
			
		||||
                        <a class="button is-primary" href="{% url "django_registration_register" %}">
 | 
			
		||||
                        <a class="button is-primary" href="{% url "account_signup" %}">
 | 
			
		||||
                            <strong>{% translate "Registrieren" %}</strong>
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <a class="button is-light" href="{% url "login" %}">
 | 
			
		||||
                        <a class="button is-light" href="{% url "account_login" %}">
 | 
			
		||||
                            <strong>{% translate "Login" %}</strong>
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    {% if rescue_organizations %}
 | 
			
		||||
        {% for rescue_organization in rescue_organizations %}
 | 
			
		||||
            <div class="cell">
 | 
			
		||||
                {% include "fellchensammlung/partials/partial-rescue-organization.html" %}
 | 
			
		||||
                {% include "fellchensammlung/partials/rescue_orgs/partial-rescue-organization.html" %}
 | 
			
		||||
            </div>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    {% else %}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load custom_tags %}
 | 
			
		||||
<div class="card">
 | 
			
		||||
    <div class="card-header">
 | 
			
		||||
        <h1 class="card-header-title">{{ org.name }}</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="card-content">
 | 
			
		||||
        <div class="block">
 | 
			
		||||
            <b><i class="fa-solid fa-location-dot"></i></b>
 | 
			
		||||
            {% if org.location %}
 | 
			
		||||
                {{ org.location }}
 | 
			
		||||
            {% else %}
 | 
			
		||||
                {{ org.location_string }}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% if org.description %}
 | 
			
		||||
                <div class="block content">
 | 
			
		||||
                    <p>{{ org.description | render_markdown }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% 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>
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
<div class="grid is-col-min-2">
 | 
			
		||||
    {% if adoption_notice.num_per_sex.F > 0 %}
 | 
			
		||||
    {% with num_per_sex=adoption_notice.num_per_sex %}
 | 
			
		||||
        {% if num_per_sex.F > 0 %}
 | 
			
		||||
            <span class="cell icon-text tag is-medium">
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.F }} </span>
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ num_per_sex.F }}</span>
 | 
			
		||||
            <span class="icon">
 | 
			
		||||
                <img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
 | 
			
		||||
                     alt="{% translate 'weibliche Tiere' %}">
 | 
			
		||||
@@ -11,9 +12,9 @@
 | 
			
		||||
        </span>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if adoption_notice.num_per_sex.I > 0 %}
 | 
			
		||||
        {% if num_per_sex.I > 0 %}
 | 
			
		||||
            <span class="cell icon-text tag is-medium">
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.I }}</span>
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ num_per_sex.I }}</span>
 | 
			
		||||
 | 
			
		||||
            <span class="icon">
 | 
			
		||||
                <img class="icon"
 | 
			
		||||
@@ -22,18 +23,18 @@
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    {% if adoption_notice.num_per_sex.M > 0 %}
 | 
			
		||||
        {% if num_per_sex.M > 0 %}
 | 
			
		||||
            <span class="cell icon-text tag is-medium">
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.M }}</span>
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ num_per_sex.M }}</span>
 | 
			
		||||
            <span class="icon">
 | 
			
		||||
                <img class="icon" src="{% static 'fellchensammlung/img/sexes/Male.png' %}"
 | 
			
		||||
                     alt="{% translate 'männliche Tiere' %}">
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    {% if adoption_notice.num_per_sex.M_N > 0 %}
 | 
			
		||||
        {% if num_per_sex.M_N > 0 %}
 | 
			
		||||
            <span class="cell icon-text tag is-medium">
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ adoption_notice.num_per_sex.M_N }}</span>
 | 
			
		||||
            <span class="has-text-weight-bold is-size-4">{{ num_per_sex.M_N }}</span>
 | 
			
		||||
            <span class="icon">
 | 
			
		||||
                <img class="icon"
 | 
			
		||||
                     src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
 | 
			
		||||
@@ -41,4 +42,5 @@
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    {% endwith %}
 | 
			
		||||
</div>
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
        <div class="grid is-col-min-15">
 | 
			
		||||
            {% for rescue_org in rescue_orgs_to_check %}
 | 
			
		||||
                <div class="cell">
 | 
			
		||||
                    {% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
 | 
			
		||||
                    {% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -50,7 +50,7 @@
 | 
			
		||||
        <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" %}
 | 
			
		||||
                    {% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -62,7 +62,7 @@
 | 
			
		||||
        <div class="grid is-col-min-15">
 | 
			
		||||
            {% for rescue_org in rescue_orgs_last_checked %}
 | 
			
		||||
                <div class="cell">
 | 
			
		||||
                    {% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
 | 
			
		||||
                    {% include "fellchensammlung/partials/rescue_orgs/partial-check-rescue-org.html" %}
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								src/fellchensammlung/templates/mfa/recovery_codes/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/fellchensammlung/templates/mfa/recovery_codes/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
{% extends "mfa/recovery_codes/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
{% element h1 %}
 | 
			
		||||
{% translate "Recovery Codes" %}
 | 
			
		||||
{% endelement %}
 | 
			
		||||
{% element p %}
 | 
			
		||||
{% blocktranslate count unused_count=unused_codes|length %}There is {{ unused_count }} out of {{ total_count }}
 | 
			
		||||
recovery codes available.{% plural %}There are {{ unused_count }} out of {{ total_count }} recovery codes
 | 
			
		||||
available.{% endblocktranslate %}
 | 
			
		||||
{% endelement %}
 | 
			
		||||
<div class="block">
 | 
			
		||||
{% element field id="recovery_codes" type="textarea" disabled=True rows=unused_codes|length readonly=True %}
 | 
			
		||||
{% slot label %}
 | 
			
		||||
{% translate "Unused codes" %}
 | 
			
		||||
{% endslot %}
 | 
			
		||||
{% comment %} djlint:off {% endcomment %}
 | 
			
		||||
{% slot value %}{% for code in unused_codes %}{% if forloop.counter0 %}
 | 
			
		||||
{% endif %}{{ code }}{% endfor %}{% endslot %}
 | 
			
		||||
{% comment %} djlint:on {% endcomment %}
 | 
			
		||||
{% endelement %}
 | 
			
		||||
</div>
 | 
			
		||||
<div class="block">
 | 
			
		||||
<div class="field is-grouped is-grouped-multiline">
 | 
			
		||||
{% if unused_codes %}
 | 
			
		||||
{% url 'mfa_download_recovery_codes' as download_url %}
 | 
			
		||||
<div class="control">
 | 
			
		||||
{% element button href=download_url %}
 | 
			
		||||
{% translate "Download codes" %}
 | 
			
		||||
{% endelement %}
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% url 'mfa_generate_recovery_codes' as generate_url %}
 | 
			
		||||
 | 
			
		||||
<div class="control">
 | 
			
		||||
{% element button href=generate_url %}
 | 
			
		||||
{% translate "Generate new codes" %}
 | 
			
		||||
{% endelement %}
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
@@ -0,0 +1,78 @@
 | 
			
		||||
{% extends "mfa/webauthn/base.html" %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load allauth %}
 | 
			
		||||
{% load humanize %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {% element h1 %}
 | 
			
		||||
        {% trans "Security Keys" %}
 | 
			
		||||
    {% endelement %}
 | 
			
		||||
    {% if authenticators|length == 0 %}
 | 
			
		||||
        {% element p %}
 | 
			
		||||
            {% blocktranslate %}No security keys have been added.{% endblocktranslate %}
 | 
			
		||||
        {% endelement %}
 | 
			
		||||
    {% else %}
 | 
			
		||||
        <article class="panel">
 | 
			
		||||
            <p class="panel-heading">{% trans "Security Keys" %}</p>
 | 
			
		||||
            {% for authenticator in authenticators %}
 | 
			
		||||
                <div class="panel-block">
 | 
			
		||||
                    <div class="level" style="width: 100%;">
 | 
			
		||||
                        <div class="level-left">
 | 
			
		||||
                            <div class="level-item">
 | 
			
		||||
                                {% if authenticator.wrap.is_passwordless is True %}
 | 
			
		||||
                                    {% element badge tags="mfa,key,primary" %}
 | 
			
		||||
                                        {% translate "Passkey" %}
 | 
			
		||||
                                    {% endelement %}
 | 
			
		||||
                                {% elif authenticator.wrap.is_passwordless is False %}
 | 
			
		||||
                                    {% element badge tags="mfa,key,secondary" %}
 | 
			
		||||
                                        {% translate "Security key" %}
 | 
			
		||||
                                    {% endelement %}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    {% element badge title=_("This key does not indicate whether it is a passkey.") tags="mfa,key,warning" %}
 | 
			
		||||
                                        {% translate "Unspecified" %}
 | 
			
		||||
                                    {% endelement %}
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="level-item">
 | 
			
		||||
                                <strong>
 | 
			
		||||
                                    {{ authenticator }}
 | 
			
		||||
                                </strong>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="level-item">
 | 
			
		||||
                                {% blocktranslate with created_at=authenticator.created_at|date:"SHORT_DATE_FORMAT" %}
 | 
			
		||||
                                    Added
 | 
			
		||||
                                    on {{ created_at }}{% endblocktranslate %}.
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="level-item">
 | 
			
		||||
                                {% if authenticator.last_used_at %}
 | 
			
		||||
                                    {% blocktranslate with last_used=authenticator.last_used_at|naturaltime %}Last used
 | 
			
		||||
                                        {{ last_used }}{% endblocktranslate %}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    Not used.
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="level-right">
 | 
			
		||||
                            <div class="level-item">
 | 
			
		||||
                                {% url 'mfa_edit_webauthn' pk=authenticator.pk as edit_url %}
 | 
			
		||||
                                {% element button tags="mfa,authenticator,edit,tool" href=edit_url %}
 | 
			
		||||
                                    {% translate "Edit" %}
 | 
			
		||||
                                {% endelement %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="level-item">
 | 
			
		||||
                                {% url 'mfa_remove_webauthn' pk=authenticator.pk as remove_url %}
 | 
			
		||||
                                {% element button tags="mfa,authenticator,danger,delete,tool" href=remove_url %}
 | 
			
		||||
                                    {% translate "Remove" %}
 | 
			
		||||
                                {% endelement %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </article>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% url 'mfa_add_webauthn' as add_url %}
 | 
			
		||||
    {% element button href=add_url %}
 | 
			
		||||
        {% translate "Add" %}
 | 
			
		||||
    {% endelement %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import datetime as datetime
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
from django.utils.translation import ngettext
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
@@ -75,3 +76,20 @@ def is_404(url):
 | 
			
		||||
        return result.status_code == 404
 | 
			
		||||
    except requests.RequestException as e:
 | 
			
		||||
        logging.warning(f"Request to {url} failed: {e}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestProfiler:
 | 
			
		||||
    data = []
 | 
			
		||||
 | 
			
		||||
    def add_status(self, status):
 | 
			
		||||
        self.data.append((time.time(), status))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def as_relative(self):
 | 
			
		||||
        first_ts = self.data[0][0]
 | 
			
		||||
        return [(datum[0] - first_ts, datum[1]) for datum in self.data]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def as_relative_with_ms(self):
 | 
			
		||||
        first_ts = self.data[0][0]
 | 
			
		||||
        return [(f"{(datum[0] - first_ts)*1000:.4}ms", datum[1]) for datum in self.data]
 | 
			
		||||
 
 | 
			
		||||
@@ -134,3 +134,14 @@ class RegularCheckStatusChoices(models.TextChoices):
 | 
			
		||||
    EXCLUDED_OTHER_ORG = "excluded_other_org", _("Exkludiert: Andere Organisation wird geprüft")
 | 
			
		||||
    EXCLUDED_SCOPE = "excluded_scope", _("Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen")
 | 
			
		||||
    EXCLUDED_OTHER = "excluded_other", _("Exkludiert: Anderer Grund")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
##########
 | 
			
		||||
## USER ##
 | 
			
		||||
##########
 | 
			
		||||
 | 
			
		||||
reason_for_signup_label = _("Grund für die Registrierung")
 | 
			
		||||
reason_for_signup_help_text = _(
 | 
			
		||||
    "Wir würden gerne wissen warum du dich registrierst, 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.")
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,8 @@ urlpatterns = [
 | 
			
		||||
         name="rescue-organization-detail"),
 | 
			
		||||
    path("tierschutzorganisationen/<int:rescue_organization_id>/exkludieren", views.exclude_from_regular_check,
 | 
			
		||||
         name="rescue-organization-exclude"),
 | 
			
		||||
    path("tierschutzorganisationen/add-exclusion-reason", views.update_exclusion_reason,
 | 
			
		||||
         name="rescue-organization-add-exclusion-reason"),
 | 
			
		||||
    path("tierschutzorganisationen/spezialisierung/<slug:species_slug>", views.specialized_rescues,
 | 
			
		||||
         name="specialized-rescue-organizations"),
 | 
			
		||||
 | 
			
		||||
@@ -92,15 +94,6 @@ urlpatterns = [
 | 
			
		||||
    path("user/notifications/", views.my_notifications, name="user-notifications"),
 | 
			
		||||
    path('user/me/export/', views.export_own_profile, name='user-me-export'),
 | 
			
		||||
 | 
			
		||||
    path('accounts/register/',
 | 
			
		||||
         registration_views.HTMLMailRegistrationView.as_view(
 | 
			
		||||
             form_class=CustomRegistrationForm,
 | 
			
		||||
             email_body_template="fellchensammlung/mail/activation_email.html",
 | 
			
		||||
         ),
 | 
			
		||||
         name='django_registration_register',
 | 
			
		||||
         ),
 | 
			
		||||
    path('accounts/', include('django_registration.backends.activation.urls')),
 | 
			
		||||
    path('accounts/', include('django.contrib.auth.urls')),
 | 
			
		||||
 | 
			
		||||
    path('change-language', views.change_language, name="change-language"),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import logging
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.views import redirect_to_login
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
@@ -37,6 +36,7 @@ from .tools.admin import clean_locations, get_unchecked_adoption_notices, deacti
 | 
			
		||||
from .tasks import post_adoption_notice_save
 | 
			
		||||
from rest_framework.authtoken.models import Token
 | 
			
		||||
 | 
			
		||||
from .tools.misc import RequestProfiler
 | 
			
		||||
from .tools.model_helpers import AdoptionNoticeStatusChoices, AdoptionNoticeProcessTemplates, RegularCheckStatusChoices
 | 
			
		||||
from .tools.search import AdoptionNoticeSearch, RescueOrgSearch
 | 
			
		||||
 | 
			
		||||
@@ -242,11 +242,17 @@ def search_important_locations(request, important_location_slug):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def search(request, templatename="fellchensammlung/search.html"):
 | 
			
		||||
    search_profile = RequestProfiler()
 | 
			
		||||
    search_profile.add_status("Start")
 | 
			
		||||
 | 
			
		||||
    # A user just visiting the search site did not search, only upon completing the search form a user has really
 | 
			
		||||
    # searched. This will toggle the "subscribe" button
 | 
			
		||||
    searched = False
 | 
			
		||||
    search_profile.add_status("Init Search")
 | 
			
		||||
    search = AdoptionNoticeSearch()
 | 
			
		||||
    search_profile.add_status("Search from request starting")
 | 
			
		||||
    search.adoption_notice_search_from_request(request)
 | 
			
		||||
    search_profile.add_status("Search from request finished")
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        searched = True
 | 
			
		||||
        if "subscribe_to_search" in request.POST:
 | 
			
		||||
@@ -266,10 +272,12 @@ def search(request, templatename="fellchensammlung/search.html"):
 | 
			
		||||
        subscribed_search = search.get_subscription_or_none(request.user)
 | 
			
		||||
    else:
 | 
			
		||||
        subscribed_search = None
 | 
			
		||||
    search_profile.add_status("End of POST")
 | 
			
		||||
    site_title = _("Suche")
 | 
			
		||||
    site_description = _("Ratten in Tierheimen und Rattenhilfen in der Nähe suchen.")
 | 
			
		||||
    canonical_url = reverse("search")
 | 
			
		||||
 | 
			
		||||
    search_profile.add_status("Start of context")
 | 
			
		||||
    context = {"adoption_notices": search.get_adoption_notices(),
 | 
			
		||||
               "search_form": search.search_form,
 | 
			
		||||
               "place_not_found": search.place_not_found,
 | 
			
		||||
@@ -286,6 +294,10 @@ def search(request, templatename="fellchensammlung/search.html"):
 | 
			
		||||
               "site_title": site_title,
 | 
			
		||||
               "site_description": site_description,
 | 
			
		||||
               "canonical_url": canonical_url}
 | 
			
		||||
    search_profile.add_status("End of context")
 | 
			
		||||
    if request.user.is_superuser:
 | 
			
		||||
        context["profile"] = search_profile.as_relative_with_ms
 | 
			
		||||
    search_profile.add_status("Finished - returing render")
 | 
			
		||||
    return render(request, templatename, context=context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -578,12 +590,20 @@ def report_detail_success(request, report_id):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def user_detail(request, user, token=None):
 | 
			
		||||
    user_detail_profile = RequestProfiler()
 | 
			
		||||
    user_detail_profile.add_status("Start")
 | 
			
		||||
    adoption_notices = AdoptionNotice.objects.filter(owner=user)
 | 
			
		||||
    user_detail_profile.add_status("Finished fetching adoption notices")
 | 
			
		||||
    context = {"user": user,
 | 
			
		||||
               "adoption_notices": AdoptionNotice.objects.filter(owner=user),
 | 
			
		||||
               "adoption_notices": adoption_notices,
 | 
			
		||||
               "notifications": Notification.objects.filter(user_to_notify=user, read=False),
 | 
			
		||||
               "search_subscriptions": SearchSubscription.objects.filter(owner=user), }
 | 
			
		||||
    user_detail_profile.add_status("End of context")
 | 
			
		||||
    if token is not None:
 | 
			
		||||
        context["token"] = token
 | 
			
		||||
    user_detail_profile.add_status("Finished - returning to renderer")
 | 
			
		||||
    if request.user.is_superuser:
 | 
			
		||||
        context["profile"] = user_detail_profile.as_relative_with_ms
 | 
			
		||||
    return render(request, 'fellchensammlung/details/detail-user.html', context=context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -654,7 +674,7 @@ def my_notifications(request):
 | 
			
		||||
    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")}
 | 
			
		||||
                                                               read=True).order_by("-read_at")[:10]}
 | 
			
		||||
    return render(request, 'fellchensammlung/notifications.html', context=context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -846,8 +866,7 @@ def rescue_organization_check(request, context=None):
 | 
			
		||||
        action = request.POST.get("action")
 | 
			
		||||
        if action == "checked":
 | 
			
		||||
            rescue_org.set_checked()
 | 
			
		||||
        elif action == "exclude":
 | 
			
		||||
            rescue_org.set_exclusion_from_checks()
 | 
			
		||||
            Log.objects.create(user=request.user, action="rescue_organization_checked", )
 | 
			
		||||
        elif action == "set_species_url":
 | 
			
		||||
            species_url_form = SpeciesURLForm(request.POST)
 | 
			
		||||
 | 
			
		||||
@@ -858,6 +877,7 @@ def rescue_organization_check(request, context=None):
 | 
			
		||||
        elif action == "update_internal_comment":
 | 
			
		||||
            comment_form = RescueOrgInternalComment(request.POST, instance=rescue_org)
 | 
			
		||||
            if comment_form.is_valid():
 | 
			
		||||
                Log.objects.create(user=request.user, action="rescue_organization_added_internal_comment", )
 | 
			
		||||
                comment_form.save()
 | 
			
		||||
 | 
			
		||||
    rescue_orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=False,
 | 
			
		||||
@@ -897,23 +917,42 @@ def rescue_organization_check_dq(request):
 | 
			
		||||
    return rescue_organization_check(request, context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@user_passes_test(user_is_trust_level_or_above)
 | 
			
		||||
def exclude_from_regular_check(request, rescue_organization_id):
 | 
			
		||||
def exclude_from_regular_check(request, rescue_organization_id, source="organization-check"):
 | 
			
		||||
    rescue_org = get_object_or_404(RescueOrganization, pk=rescue_organization_id)
 | 
			
		||||
    if request.method == "POST":
 | 
			
		||||
        form = UpdateRescueOrgRegularCheckStatus(request.POST, instance=rescue_org)
 | 
			
		||||
        if form.is_valid():
 | 
			
		||||
            form.save()
 | 
			
		||||
            if form.cleaned_data["regular_check_status"] != RegularCheckStatusChoices.REGULAR_CHECK:
 | 
			
		||||
                rescue_org.exclude_from_check = True
 | 
			
		||||
            to_be_excluded = form.cleaned_data["regular_check_status"] != RegularCheckStatusChoices.REGULAR_CHECK
 | 
			
		||||
            rescue_org.exclude_from_check = to_be_excluded
 | 
			
		||||
            rescue_org.save()
 | 
			
		||||
            return redirect(reverse("organization-check"))
 | 
			
		||||
            if to_be_excluded:
 | 
			
		||||
                Log.objects.create(user=request.user,
 | 
			
		||||
                                   action="rescue_organization_excluded_from_check",
 | 
			
		||||
                                   text=f"New status: {form.cleaned_data["regular_check_status"]}")
 | 
			
		||||
 | 
			
		||||
            return redirect(reverse(source))
 | 
			
		||||
    else:
 | 
			
		||||
        form = UpdateRescueOrgRegularCheckStatus(instance=rescue_org)
 | 
			
		||||
    context = {"form": form, rescue_org: rescue_org}
 | 
			
		||||
    org_meta = rescue_org._meta
 | 
			
		||||
    context = {"form": form, "org": rescue_org, "org_meta": org_meta}
 | 
			
		||||
    return render(request, 'fellchensammlung/forms/form-exclude-org-from-check.html', context=context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@user_passes_test(user_is_trust_level_or_above)
 | 
			
		||||
def update_exclusion_reason(request):
 | 
			
		||||
    """
 | 
			
		||||
    This view will redirect to update a rescue org that not yet has an exclusion reason but is excluded
 | 
			
		||||
    """
 | 
			
		||||
    orgs_to_check = RescueOrganization.objects.filter(exclude_from_check=True,
 | 
			
		||||
                                                      regular_check_status=RegularCheckStatusChoices.REGULAR_CHECK)
 | 
			
		||||
    if orgs_to_check.count() > 0:
 | 
			
		||||
        return exclude_from_regular_check(request, orgs_to_check[0].pk,
 | 
			
		||||
                                          source="rescue-organization-add-exclusion-reason")
 | 
			
		||||
    else:
 | 
			
		||||
        return render(request, "fellchensammlung/errors/404.html", status=404)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@user_passes_test(user_is_trust_level_or_above)
 | 
			
		||||
def moderation_tools_overview(request):
 | 
			
		||||
    context = None
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/3.2/topics/settings/
 | 
			
		||||
For the full list of settings and their values, see
 | 
			
		||||
https://docs.djangoproject.com/en/3.2/ref/settings/
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import os
 | 
			
		||||
import configparser
 | 
			
		||||
@@ -74,6 +74,10 @@ except configparser.NoSectionError:
 | 
			
		||||
    raise BaseException("No config found or no Django Secret is configured!")
 | 
			
		||||
DEBUG = config.getboolean('django', 'debug', fallback=False)
 | 
			
		||||
 | 
			
		||||
# Internal IPs
 | 
			
		||||
raw_config_value = config.get("django", "internal_ips", fallback=[])
 | 
			
		||||
INTERNAL_IPS = json.loads(raw_config_value)
 | 
			
		||||
 | 
			
		||||
""" DATABASE """
 | 
			
		||||
DB_BACKEND = config.get("database", "backend", fallback="sqlite3")
 | 
			
		||||
DB_NAME = config.get("database", "name", fallback="notfellchen.sqlite3")
 | 
			
		||||
@@ -85,6 +89,7 @@ DB_HOST = config.get("database", "host", fallback='')
 | 
			
		||||
BASE_DIR = Path(__file__).resolve().parent.parent
 | 
			
		||||
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
""" CELERY + KEYDB """
 | 
			
		||||
CELERY_BROKER_URL = config.get("celery", "broker", fallback="redis://localhost:6379/0")
 | 
			
		||||
CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localhost:6379/0")
 | 
			
		||||
@@ -130,6 +135,37 @@ ACCOUNT_ACTIVATION_DAYS = 7  # One-week activation window
 | 
			
		||||
REGISTRATION_OPEN = True
 | 
			
		||||
REGISTRATION_SALT = "notfellchen"
 | 
			
		||||
 | 
			
		||||
AUTHENTICATION_BACKENDS = [
 | 
			
		||||
    # Needed to login by username in Django admin, regardless of `allauth`
 | 
			
		||||
    'django.contrib.auth.backends.ModelBackend',
 | 
			
		||||
 | 
			
		||||
    # `allauth` specific authentication methods, such as login by email
 | 
			
		||||
    'allauth.account.auth_backends.AuthenticationBackend',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
 | 
			
		||||
ACCOUNT_EMAIL_VERIFICATION_BY_CODE_ENABLED = True
 | 
			
		||||
 | 
			
		||||
ACCOUNT_SIGNUP_FIELDS = ['username*', "email*", "password1*"]
 | 
			
		||||
 | 
			
		||||
ACCOUNT_SIGNUP_FORM_CLASS = 'fellchensammlung.forms.AddedRegistrationForm'
 | 
			
		||||
 | 
			
		||||
MFA_SUPPORTED_TYPES = ["totp",
 | 
			
		||||
                       "webauthn",
 | 
			
		||||
                       "recovery_codes"]
 | 
			
		||||
 | 
			
		||||
MFA_TOTP_TOLERANCE = 1
 | 
			
		||||
MFA_TOTP_ISSUER = config.get('security', 'totp_issuer', fallback="Notfellchen")
 | 
			
		||||
 | 
			
		||||
MFA_PASSKEY_LOGIN_ENABLED = True
 | 
			
		||||
MFA_PASSKEY_SIGNUP_ENABLED = True
 | 
			
		||||
 | 
			
		||||
# Optional -- use for local development only: the WebAuthn uses the
 | 
			
		||||
#``fido2`` package, and versions up to including version 1.1.3 do not
 | 
			
		||||
# regard localhost as a secure origin, which is problematic during
 | 
			
		||||
# local development and testing.
 | 
			
		||||
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = config.get('security', 'webauth_allow_insecure_origin', fallback=False)
 | 
			
		||||
 | 
			
		||||
""" SECURITY.TXT """
 | 
			
		||||
SEC_CONTACT = config.get("security", "Contact", fallback="julian-samuel@gebuehr.net")
 | 
			
		||||
SEC_EXPIRES = config.get("security", "Expires", fallback="2028-03-17T07:00:00.000Z")
 | 
			
		||||
@@ -182,7 +218,11 @@ INSTALLED_APPS = [
 | 
			
		||||
    'django.contrib.auth',
 | 
			
		||||
    'django.contrib.contenttypes',
 | 
			
		||||
    'django.contrib.sessions',
 | 
			
		||||
    "django.contrib.humanize",
 | 
			
		||||
    'django.contrib.messages',
 | 
			
		||||
    'allauth',
 | 
			
		||||
    'allauth.account',
 | 
			
		||||
    'allauth.mfa',
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    "django.contrib.sitemaps",
 | 
			
		||||
    'fontawesomefree',
 | 
			
		||||
@@ -193,11 +233,13 @@ INSTALLED_APPS = [
 | 
			
		||||
    'rest_framework.authtoken',
 | 
			
		||||
    'drf_spectacular',
 | 
			
		||||
    'drf_spectacular_sidecar',  # required for Django collectstatic discovery
 | 
			
		||||
    'widget_tweaks'
 | 
			
		||||
    'widget_tweaks',
 | 
			
		||||
    "debug_toolbar",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
    'django.middleware.security.SecurityMiddleware',
 | 
			
		||||
    "debug_toolbar.middleware.DebugToolbarMiddleware",
 | 
			
		||||
    # Static file serving & caching
 | 
			
		||||
    'whitenoise.middleware.WhiteNoiseMiddleware',
 | 
			
		||||
    'django.contrib.sessions.middleware.SessionMiddleware',
 | 
			
		||||
@@ -208,6 +250,8 @@ MIDDLEWARE = [
 | 
			
		||||
    'django.contrib.auth.middleware.AuthenticationMiddleware',
 | 
			
		||||
    'django.contrib.messages.middleware.MessageMiddleware',
 | 
			
		||||
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
			
		||||
    # allauth middleware, needs to be after message middleware
 | 
			
		||||
    "allauth.account.middleware.AccountMiddleware",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
ROOT_URLCONF = 'notfellchen.urls'
 | 
			
		||||
@@ -222,6 +266,7 @@ TEMPLATES = [
 | 
			
		||||
        'OPTIONS': {
 | 
			
		||||
            'context_processors': [
 | 
			
		||||
                'django.template.context_processors.debug',
 | 
			
		||||
                # Needed for allauth
 | 
			
		||||
                'django.template.context_processors.request',
 | 
			
		||||
                'django.contrib.auth.context_processors.auth',
 | 
			
		||||
                'django.template.context_processors.media',
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,14 @@ from django.conf.urls.i18n import i18n_patterns
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
from django.urls import include, path
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
from django.conf.urls.static import static
 | 
			
		||||
 | 
			
		||||
from debug_toolbar.toolbar import debug_toolbar_urls
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('admin/', admin.site.urls),
 | 
			
		||||
]
 | 
			
		||||
    path('accounts/', include('allauth.urls')),
 | 
			
		||||
] + debug_toolbar_urls()
 | 
			
		||||
 | 
			
		||||
urlpatterns += i18n_patterns(
 | 
			
		||||
    path("", include("fellchensammlung.urls")),
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
            <div class="block">
 | 
			
		||||
                <a class="button is-warning" href="{% url 'password_reset' %}">{% translate "Passwort vergessen?" %}</a>
 | 
			
		||||
                <a class="button is-link"
 | 
			
		||||
                   href="{% url 'django_registration_register' %}">{% translate "Registrieren" %}</a>
 | 
			
		||||
                   href="{% url 'account_signup' %}">{% translate "Registrieren" %}</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user