Compare commits
3 Commits
88987a973e
...
ci-test-co
Author | SHA1 | Date | |
---|---|---|---|
c9f46d7547 | |||
9f23f5768c | |||
19210f90cd |
2
.gitignore
vendored
@@ -4,7 +4,7 @@
|
|||||||
notfellchen
|
notfellchen
|
||||||
|
|
||||||
# Media storage
|
# Media storage
|
||||||
/static
|
static
|
||||||
media
|
media
|
||||||
|
|
||||||
|
|
||||||
|
@@ -6,6 +6,9 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- cd docs && make html
|
- cd docs && make html
|
||||||
|
|
||||||
|
when:
|
||||||
|
event: [ tag, push ]
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
image: appleboy/drone-scp
|
image: appleboy/drone-scp
|
||||||
settings:
|
settings:
|
||||||
@@ -19,6 +22,8 @@ steps:
|
|||||||
source: docs/_build/html/
|
source: docs/_build/html/
|
||||||
key:
|
key:
|
||||||
from_secret: ssh_key
|
from_secret: ssh_key
|
||||||
|
when:
|
||||||
|
event: [ tag, push ]
|
||||||
|
|
||||||
|
|
||||||
|
|
14
.woodpecker/test.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
steps:
|
||||||
|
test:
|
||||||
|
image: python
|
||||||
|
commands:
|
||||||
|
- python -m pip install '.[develop]'
|
||||||
|
- coverage run --source='.' src/manage.py test src && coverage html
|
||||||
|
- coverage html
|
||||||
|
- cat htmlcov/index.html
|
||||||
|
when:
|
||||||
|
event: [tag, push]
|
||||||
|
|
||||||
|
|
@@ -1,105 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
DEFAULT_OSM_DATA_FILE = "export.geojson"
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
"""Parse command-line arguments."""
|
|
||||||
parser = argparse.ArgumentParser(description="Upload animal shelter data to the Notfellchen API.")
|
|
||||||
parser.add_argument("--api-token", type=str, help="API token for authentication.")
|
|
||||||
parser.add_argument("--instance", type=str, help="API instance URL.")
|
|
||||||
parser.add_argument("--data-file", type=str, help="Path to the GeoJSON file containing (only) animal shelters.")
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
|
||||||
"""Get configuration from environment variables or command-line arguments."""
|
|
||||||
args = parse_args()
|
|
||||||
|
|
||||||
api_token = args.api_token or os.getenv("NOTFELLCHEN_API_TOKEN")
|
|
||||||
instance = args.instance or os.getenv("NOTFELLCHEN_INSTANCE")
|
|
||||||
data_file = args.data_file or os.getenv("NOTFELLCHEN_DATA_FILE", DEFAULT_OSM_DATA_FILE)
|
|
||||||
|
|
||||||
if not api_token or not instance:
|
|
||||||
raise ValueError("API token and instance URL must be provided via environment variables or CLI arguments.")
|
|
||||||
|
|
||||||
return api_token, instance, data_file
|
|
||||||
|
|
||||||
|
|
||||||
def get_or_none(data, key):
|
|
||||||
if key in data["properties"].keys():
|
|
||||||
return data["properties"][key]
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def choose(keys, data, replace=False):
|
|
||||||
for key in keys:
|
|
||||||
if key in data.keys():
|
|
||||||
if replace:
|
|
||||||
return data[key].replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
|
|
||||||
else:
|
|
||||||
return data[key]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def add(value, platform):
|
|
||||||
if value != "":
|
|
||||||
if value.find(platform) == -1:
|
|
||||||
return f"https://www.{platform}.com/{value}"
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def https(value):
|
|
||||||
if value is not None and value != "":
|
|
||||||
value = value.replace("http://", "")
|
|
||||||
if value.find("https") == -1:
|
|
||||||
return f"https://{value}"
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
api_token, instance, data_file = get_config()
|
|
||||||
# Set headers and endpoint
|
|
||||||
endpoint = f"{instance}/api/organizations/"
|
|
||||||
h = {'Authorization': f'Token {api_token}', "content-type": "application/json"}
|
|
||||||
|
|
||||||
with open(data_file, encoding="utf8") as f:
|
|
||||||
d = json.load(f)
|
|
||||||
|
|
||||||
for idx, tierheim in tqdm(enumerate(d["features"])):
|
|
||||||
|
|
||||||
if "name" not in tierheim["properties"].keys() or "addr:city" not in tierheim["properties"].keys():
|
|
||||||
continue
|
|
||||||
|
|
||||||
data = {"name": tierheim["properties"]["name"],
|
|
||||||
"location_string": f"{get_or_none(tierheim, "addr:street")} {get_or_none(tierheim, "addr:housenumber")}, {get_or_none(tierheim, "addr:postcode")} {tierheim["properties"]["addr:city"]}",
|
|
||||||
"phone_number": choose(("contact:phone", "phone"), tierheim["properties"], replace=True),
|
|
||||||
"fediverse_profile": get_or_none(tierheim, "contact:mastodon"),
|
|
||||||
"facebook": https(add(get_or_none(tierheim, "contact:facebook"), "facebook")),
|
|
||||||
"instagram": https(add(get_or_none(tierheim, "contact:instagram"), "instagram")),
|
|
||||||
"website": https(choose(("contact:website", "website"), tierheim["properties"])),
|
|
||||||
"email": choose(("contact:email", "email"), tierheim["properties"]),
|
|
||||||
"description": get_or_none(tierheim, "opening_hours"),
|
|
||||||
"external_object_identifier": f"{tierheim["id"]}",
|
|
||||||
"external_source_identifier": "OSM"
|
|
||||||
}
|
|
||||||
|
|
||||||
result = requests.post(endpoint, json=data, headers=h)
|
|
||||||
|
|
||||||
if result.status_code != 201:
|
|
||||||
print(f"{idx} {tierheim["properties"]["name"]}:{result.status_code} {result.json()}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@@ -7,7 +7,7 @@ from django.urls import reverse
|
|||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
from .models import User, Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
|
||||||
SpeciesSpecificURL, ImportantLocation
|
SpeciesSpecificURL
|
||||||
|
|
||||||
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \
|
||||||
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, BaseNotification
|
Comment, Report, Announcement, AdoptionNoticeStatus, User, Subscriptions, BaseNotification
|
||||||
@@ -94,14 +94,12 @@ class ReportAdoptionNoticeAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
reported_content_link.short_description = "Reported Content"
|
reported_content_link.short_description = "Reported Content"
|
||||||
|
|
||||||
|
|
||||||
class SpeciesSpecificURLInline(admin.StackedInline):
|
class SpeciesSpecificURLInline(admin.StackedInline):
|
||||||
model = SpeciesSpecificURL
|
model = SpeciesSpecificURL
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RescueOrganization)
|
@admin.register(RescueOrganization)
|
||||||
class RescueOrganizationAdmin(admin.ModelAdmin):
|
class RescueOrganizationAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("name", "description", "internal_comment", "location_string")
|
search_fields = ("name","description", "internal_comment", "location_string")
|
||||||
list_display = ("name", "trusted", "allows_using_materials", "website")
|
list_display = ("name", "trusted", "allows_using_materials", "website")
|
||||||
list_filter = ("allows_using_materials", "trusted",)
|
list_filter = ("allows_using_materials", "trusted",)
|
||||||
|
|
||||||
@@ -124,46 +122,14 @@ class CommentAdmin(admin.ModelAdmin):
|
|||||||
class BaseNotificationAdmin(admin.ModelAdmin):
|
class BaseNotificationAdmin(admin.ModelAdmin):
|
||||||
list_filter = ("user", "read")
|
list_filter = ("user", "read")
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SearchSubscription)
|
@admin.register(SearchSubscription)
|
||||||
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
class SearchSubscriptionAdmin(admin.ModelAdmin):
|
||||||
list_filter = ("owner",)
|
list_filter = ("owner",)
|
||||||
|
|
||||||
|
|
||||||
class ImportantLocationInline(admin.StackedInline):
|
|
||||||
model = ImportantLocation
|
|
||||||
|
|
||||||
|
|
||||||
class IsImportantListFilter(admin.SimpleListFilter):
|
|
||||||
# See https://docs.djangoproject.com/en/5.1/ref/contrib/admin/filters/#modeladmin-list-filters
|
|
||||||
title = _('Is Important Location?')
|
|
||||||
|
|
||||||
parameter_name = 'important'
|
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
|
||||||
return (
|
|
||||||
('is_important', _('Important Location')),
|
|
||||||
('is_normal', _('Normal Location')),
|
|
||||||
)
|
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
|
||||||
if self.value() == 'is_important':
|
|
||||||
return queryset.filter(importantlocation__isnull=False)
|
|
||||||
else:
|
|
||||||
return queryset.filter(importantlocation__isnull=True)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Location)
|
|
||||||
class LocationAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ("name__icontains", "city__icontains")
|
|
||||||
list_filter = [IsImportantListFilter]
|
|
||||||
inlines = [
|
|
||||||
ImportantLocationInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Animal)
|
admin.site.register(Animal)
|
||||||
admin.site.register(Species)
|
admin.site.register(Species)
|
||||||
|
admin.site.register(Location)
|
||||||
admin.site.register(Rule)
|
admin.site.register(Rule)
|
||||||
admin.site.register(Image)
|
admin.site.register(Image)
|
||||||
admin.site.register(ModerationAction)
|
admin.site.register(ModerationAction)
|
||||||
|
@@ -1,35 +1,12 @@
|
|||||||
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image, Location
|
from ..models import Animal, RescueOrganization, AdoptionNotice, Species, Image
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
class AdoptionNoticeSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
location = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=Location.objects.all(),
|
|
||||||
required=False,
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
location_details = serializers.StringRelatedField(source='location', read_only=True)
|
|
||||||
organization = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=RescueOrganization.objects.all(),
|
|
||||||
required=False,
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
organization = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=RescueOrganization.objects.all(),
|
|
||||||
required=False,
|
|
||||||
allow_null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
photos = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=Image.objects.all(),
|
|
||||||
many=True,
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdoptionNotice
|
model = AdoptionNotice
|
||||||
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information",
|
fields = ['created_at', 'last_checked', "searching_since", "name", "description", "further_information",
|
||||||
"group_only", "location", "location_details", "organization", "photos"]
|
"group_only"]
|
||||||
|
|
||||||
|
|
||||||
class AnimalCreateSerializer(serializers.ModelSerializer):
|
class AnimalCreateSerializer(serializers.ModelSerializer):
|
||||||
@@ -37,14 +14,12 @@ class AnimalCreateSerializer(serializers.ModelSerializer):
|
|||||||
model = Animal
|
model = Animal
|
||||||
fields = ["name", "date_of_birth", "description", "species", "sex", "adoption_notice"]
|
fields = ["name", "date_of_birth", "description", "species", "sex", "adoption_notice"]
|
||||||
|
|
||||||
|
|
||||||
class RescueOrgSerializer(serializers.ModelSerializer):
|
class RescueOrgSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RescueOrganization
|
model = RescueOrganization
|
||||||
fields = ["name", "location_string", "instagram", "facebook", "fediverse_profile", "email", "phone_number",
|
fields = ["name", "location_string", "instagram", "facebook", "fediverse_profile", "email", "phone_number",
|
||||||
"website", "description", "external_object_identifier", "external_source_identifier"]
|
"website", "description", "external_object_identifier", "external_source_identifier"]
|
||||||
|
|
||||||
|
|
||||||
class AnimalGetSerializer(serializers.ModelSerializer):
|
class AnimalGetSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Animal
|
model = Animal
|
||||||
@@ -76,9 +51,3 @@ class SpeciesSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Species
|
model = Species
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class LocationSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Location
|
|
||||||
fields = "__all__"
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import (
|
from .views import (
|
||||||
AdoptionNoticeApiView,
|
AdoptionNoticeApiView,
|
||||||
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView, LocationApiView
|
AnimalApiView, RescueOrganizationApiView, AddImageApiView, SpeciesApiView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -13,5 +13,4 @@ urlpatterns = [
|
|||||||
path("organizations/<int:id>/", RescueOrganizationApiView.as_view(), name="api-organization-detail"),
|
path("organizations/<int:id>/", RescueOrganizationApiView.as_view(), name="api-organization-detail"),
|
||||||
path("images/", AddImageApiView.as_view(), name="api-add-image"),
|
path("images/", AddImageApiView.as_view(), name="api-add-image"),
|
||||||
path("species/", SpeciesApiView.as_view(), name="api-species-list"),
|
path("species/", SpeciesApiView.as_view(), name="api-species-list"),
|
||||||
path("locations/", LocationApiView.as_view(), name="api-locations-list"),
|
|
||||||
]
|
]
|
||||||
|
@@ -1,11 +1,8 @@
|
|||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from fellchensammlung.api.serializers import LocationSerializer
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from fellchensammlung.models import AdoptionNotice, Animal, Log, TrustLevel, Location
|
from fellchensammlung.models import AdoptionNotice, Animal, Log, TrustLevel
|
||||||
from fellchensammlung.tasks import post_adoption_notice_save, post_rescue_org_save
|
from fellchensammlung.tasks import post_adoption_notice_save
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
@@ -19,7 +16,6 @@ from .serializers import (
|
|||||||
from fellchensammlung.models import Animal, RescueOrganization, AdoptionNotice, Species, Image
|
from fellchensammlung.models import Animal, RescueOrganization, AdoptionNotice, Species, Image
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeApiView(APIView):
|
class AdoptionNoticeApiView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@@ -88,6 +84,7 @@ class AdoptionNoticeApiView(APIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AnimalApiView(APIView):
|
class AnimalApiView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@@ -121,7 +118,6 @@ class AnimalApiView(APIView):
|
|||||||
)
|
)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganizationApiView(APIView):
|
class RescueOrganizationApiView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@@ -133,44 +129,14 @@ class RescueOrganizationApiView(APIView):
|
|||||||
'description': 'ID of the rescue organization to retrieve.',
|
'description': 'ID of the rescue organization to retrieve.',
|
||||||
'type': int
|
'type': int
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'name': 'trusted',
|
|
||||||
'required': False,
|
|
||||||
'description': 'Filter by trusted status (true/false).',
|
|
||||||
'type': bool
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'external_object_identifier',
|
|
||||||
'required': False,
|
|
||||||
'description': 'Filter by external object identifier. Use "None" to filter for an empty field',
|
|
||||||
'type': str
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'external_source_identifier',
|
|
||||||
'required': False,
|
|
||||||
'description': 'Filter by external source identifier. Use "None" to filter for an empty field',
|
|
||||||
'type': str
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'search',
|
|
||||||
'required': False,
|
|
||||||
'description': 'Search by organization name or location name/city.',
|
|
||||||
'type': str
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
responses={200: RescueOrganizationSerializer(many=True)}
|
responses={200: RescueOrganizationSerializer(many=True)}
|
||||||
)
|
)
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Get list of rescue organizations or a specific organization by ID or get a list with available filters for
|
Get list of rescue organizations or a specific organization by ID.
|
||||||
- external_object_identifier
|
|
||||||
- external_source_identifier
|
|
||||||
"""
|
"""
|
||||||
org_id = request.query_params.get("id")
|
org_id = kwargs.get("id")
|
||||||
external_object_identifier = request.query_params.get("external_object_identifier")
|
|
||||||
external_source_identifier = request.query_params.get("external_source_identifier")
|
|
||||||
search_query = request.query_params.get("search")
|
|
||||||
|
|
||||||
if org_id:
|
if org_id:
|
||||||
try:
|
try:
|
||||||
organization = RescueOrganization.objects.get(pk=org_id)
|
organization = RescueOrganization.objects.get(pk=org_id)
|
||||||
@@ -178,33 +144,14 @@ class RescueOrganizationApiView(APIView):
|
|||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
except RescueOrganization.DoesNotExist:
|
except RescueOrganization.DoesNotExist:
|
||||||
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
organizations = RescueOrganization.objects.all()
|
organizations = RescueOrganization.objects.all()
|
||||||
|
|
||||||
if external_object_identifier:
|
|
||||||
if external_object_identifier == "None":
|
|
||||||
external_object_identifier = None
|
|
||||||
organizations = organizations.filter(external_object_identifier=external_object_identifier)
|
|
||||||
|
|
||||||
if external_source_identifier:
|
|
||||||
if external_source_identifier == "None":
|
|
||||||
external_source_identifier = None
|
|
||||||
organizations = organizations.filter(external_source_identifier=external_source_identifier)
|
|
||||||
if search_query:
|
|
||||||
organizations = organizations.filter(
|
|
||||||
Q(name__icontains=search_query) |
|
|
||||||
Q(location_string__icontains=search_query) |
|
|
||||||
Q(location__name__icontains=search_query) |
|
|
||||||
Q(location__city__icontains=search_query)
|
|
||||||
)
|
|
||||||
|
|
||||||
serializer = RescueOrganizationSerializer(organizations, many=True, context={"request": request})
|
serializer = RescueOrganizationSerializer(organizations, many=True, context={"request": request})
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=RescueOrgSerializer,
|
request=RescueOrgSerializer, # Document the request body
|
||||||
responses={201: 'Rescue organization created successfully!'}
|
responses={201: 'Rescue organization created/updated successfully!'}
|
||||||
)
|
)
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -212,39 +159,11 @@ class RescueOrganizationApiView(APIView):
|
|||||||
"""
|
"""
|
||||||
serializer = RescueOrgSerializer(data=request.data, context={"request": request})
|
serializer = RescueOrgSerializer(data=request.data, context={"request": request})
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
rescue_org = serializer.save()
|
rescue_org = serializer.save(owner=request.user)
|
||||||
# Add the location
|
|
||||||
post_rescue_org_save.delay_on_commit(rescue_org.pk)
|
|
||||||
return Response(
|
return Response(
|
||||||
{"message": "Rescue organization created successfully!", "id": rescue_org.id},
|
{"message": "Rescue organization created/updated successfully!", "id": rescue_org.id},
|
||||||
status=status.HTTP_201_CREATED,
|
status=status.HTTP_201_CREATED,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
@extend_schema(
|
|
||||||
request=RescueOrgSerializer,
|
|
||||||
responses={200: 'Rescue organization updated successfully!'}
|
|
||||||
)
|
|
||||||
def patch(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Partially update a rescue organization.
|
|
||||||
"""
|
|
||||||
org_id = kwargs.get("id")
|
|
||||||
if not org_id:
|
|
||||||
return Response({"error": "ID is required for updating."}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
try:
|
|
||||||
organization = RescueOrganization.objects.get(pk=org_id)
|
|
||||||
except RescueOrganization.DoesNotExist:
|
|
||||||
return Response({"error": "Organization not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
serializer = RescueOrgSerializer(organization, data=request.data, partial=True, context={"request": request})
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return Response({"message": "Rescue organization updated successfully!"}, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class AddImageApiView(APIView):
|
class AddImageApiView(APIView):
|
||||||
@@ -291,63 +210,3 @@ class SpeciesApiView(APIView):
|
|||||||
species = Species.objects.all()
|
species = Species.objects.all()
|
||||||
serializer = SpeciesSerializer(species, many=True, context={"request": request})
|
serializer = SpeciesSerializer(species, many=True, context={"request": request})
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
class LocationApiView(APIView):
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
parameters=[
|
|
||||||
{
|
|
||||||
'name': 'id',
|
|
||||||
'required': False,
|
|
||||||
'description': 'ID of the location to retrieve.',
|
|
||||||
'type': int
|
|
||||||
},
|
|
||||||
],
|
|
||||||
responses={200: LocationSerializer(many=True)}
|
|
||||||
)
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Retrieve a location
|
|
||||||
"""
|
|
||||||
location_id = kwargs.get("id")
|
|
||||||
if location_id:
|
|
||||||
try:
|
|
||||||
location = Location.objects.get(pk=location_id)
|
|
||||||
serializer = LocationSerializer(location, context={"request": request})
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
except Location.DoesNotExist:
|
|
||||||
return Response({"error": "Location not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
||||||
locations = Location.objects.all()
|
|
||||||
serializer = LocationSerializer(locations, many=True, context={"request": request})
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
@extend_schema(
|
|
||||||
request=LocationSerializer,
|
|
||||||
responses={201: 'Location created successfully!'}
|
|
||||||
)
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
API view to add a location
|
|
||||||
"""
|
|
||||||
serializer = LocationSerializer(data=request.data, context={'request': request})
|
|
||||||
if not serializer.is_valid():
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
location = serializer.save()
|
|
||||||
|
|
||||||
# Log the action
|
|
||||||
Log.objects.create(
|
|
||||||
user=request.user,
|
|
||||||
action="add_location",
|
|
||||||
text=f"{request.user} added adoption notice {location.pk} via API",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Return success response with new adoption notice details
|
|
||||||
return Response(
|
|
||||||
{"message": "Location created successfully!", "id": location.pk},
|
|
||||||
status=status.HTTP_201_CREATED,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -22,15 +22,6 @@ class DateInput(forms.DateInput):
|
|||||||
input_type = 'date'
|
input_type = 'date'
|
||||||
|
|
||||||
|
|
||||||
class BulmaAdoptionNoticeForm(forms.ModelForm):
|
|
||||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AdoptionNotice
|
|
||||||
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string",
|
|
||||||
"organization"]
|
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeForm(forms.ModelForm):
|
class AdoptionNoticeForm(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:
|
||||||
@@ -136,9 +127,8 @@ class ImageForm(forms.ModelForm):
|
|||||||
self.helper.form_method = 'post'
|
self.helper.form_method = 'post'
|
||||||
|
|
||||||
if in_flow:
|
if in_flow:
|
||||||
submits = Div(Submit('submit', _('Speichern')),
|
submits= Div(Submit('submit', _('Speichern')),
|
||||||
Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')),
|
Submit('save-and-add-another', _('Speichern und weiteres Foto hinzufügen')), css_class="container-edit-buttons")
|
||||||
css_class="container-edit-buttons")
|
|
||||||
else:
|
else:
|
||||||
submits = Fieldset(Submit('submit', _('Speichern')), css_class="container-edit-buttons")
|
submits = Fieldset(Submit('submit', _('Speichern')), css_class="container-edit-buttons")
|
||||||
self.helper.layout = Layout(
|
self.helper.layout = Layout(
|
||||||
@@ -150,6 +140,7 @@ class ImageForm(forms.ModelForm):
|
|||||||
submits
|
submits
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Image
|
model = Image
|
||||||
fields = ('image', 'alt_text')
|
fields = ('image', 'alt_text')
|
||||||
@@ -173,7 +164,7 @@ class CommentForm(forms.ModelForm):
|
|||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
self.helper.form_class = 'form-comments'
|
self.helper.form_class = 'form-comments'
|
||||||
self.helper.add_input(Hidden('action', 'comment'))
|
self.helper.add_input(Hidden('action', 'comment'))
|
||||||
self.helper.add_input(Submit('submit', _('Kommentieren'), css_class="button is-primary"))
|
self.helper.add_input(Submit('submit', _('Kommentieren'), css_class="btn2"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Comment
|
model = Comment
|
||||||
@@ -190,8 +181,7 @@ 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=_(
|
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."))
|
||||||
"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)
|
||||||
@@ -203,10 +193,7 @@ class CustomRegistrationForm(RegistrationForm):
|
|||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeSearchForm(forms.Form):
|
class AdoptionNoticeSearchForm(forms.Form):
|
||||||
template_name = "fellchensammlung/forms/form_snippets.html"
|
|
||||||
|
|
||||||
sex = forms.ChoiceField(choices=SexChoicesWithAll, label=_("Geschlecht"), required=False,
|
sex = forms.ChoiceField(choices=SexChoicesWithAll, label=_("Geschlecht"), required=False,
|
||||||
initial=SexChoicesWithAll.ALL)
|
initial=SexChoicesWithAll.ALL)
|
||||||
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.ONE_HUNDRED,
|
max_distance = forms.ChoiceField(choices=DistanceChoices, initial=DistanceChoices.ONE_HUNDRED, label=_("Suchradius"))
|
||||||
label=_("Suchradius"))
|
|
||||||
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
location_string = forms.CharField(max_length=100, label=_("Stadt"), required=False)
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-03-09 08:31
|
|
||||||
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0037_alter_basenotification_title'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='rescueorganization',
|
|
||||||
name='last_checked',
|
|
||||||
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Datum der letzten Prüfung'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-03-09 16:44
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0038_rescueorganization_last_checked'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='rescueorganization',
|
|
||||||
name='last_checked',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Datum der letzten Prüfung'),
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-03-20 23:27
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0039_alter_rescueorganization_last_checked'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='rescueorganization',
|
|
||||||
name='allows_using_materials',
|
|
||||||
field=models.CharField(choices=[('allowed', 'Usage allowed'), ('requested', 'Usage requested'), ('denied', 'Usage denied'), ('other', "It's complicated"), ('not_asked', 'Not asked')], default='not_asked', max_length=200, verbose_name='Erlaubt Nutzung von Inhalten'),
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,42 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-04-06 06:37
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0040_alter_rescueorganization_allows_using_materials'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='location',
|
|
||||||
name='city',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='location',
|
|
||||||
name='country',
|
|
||||||
field=models.CharField(blank=True, help_text='Standardisierter Ländercode nach ISO 3166-1 ALPHA-2', max_length=2, null=True, verbose_name='Ländercode'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='location',
|
|
||||||
name='housenumber',
|
|
||||||
field=models.CharField(blank=True, max_length=20, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='location',
|
|
||||||
name='postcode',
|
|
||||||
field=models.CharField(blank=True, max_length=20, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='location',
|
|
||||||
name='street',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='rescueorganization',
|
|
||||||
unique_together={('external_object_identifier', 'external_source_identifier')},
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-04-24 17:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0041_location_city_location_country_location_housenumber_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='location',
|
|
||||||
name='county',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-04-24 17:41
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0042_location_county'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='location',
|
|
||||||
old_name='country',
|
|
||||||
new_name='countrycode',
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-04-26 22:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0043_rename_country_location_countrycode'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='location',
|
|
||||||
name='place_id',
|
|
||||||
field=models.CharField(max_length=200),
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-04-27 11:31
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0044_alter_location_place_id'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ImportantLocation',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('slug', models.SlugField(unique=True)),
|
|
||||||
('name', models.CharField(max_length=200)),
|
|
||||||
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.location')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.4 on 2025-04-27 11:51
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('fellchensammlung', '0045_importantlocation'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='importantlocation',
|
|
||||||
name='location',
|
|
||||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='fellchensammlung.location'),
|
|
||||||
),
|
|
||||||
]
|
|
@@ -3,7 +3,6 @@ from random import choices
|
|||||||
from tabnanny import verbose
|
from tabnanny import verbose
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.defaultfilters import slugify
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@@ -40,28 +39,15 @@ class Language(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Location(models.Model):
|
class Location(models.Model):
|
||||||
place_id = models.CharField(max_length=200) # OSM id
|
place_id = models.IntegerField() # OSM id
|
||||||
latitude = models.FloatField()
|
latitude = models.FloatField()
|
||||||
longitude = models.FloatField()
|
longitude = models.FloatField()
|
||||||
name = models.CharField(max_length=2000)
|
name = models.CharField(max_length=2000)
|
||||||
city = models.CharField(max_length=200, blank=True, null=True)
|
|
||||||
housenumber = models.CharField(max_length=20, blank=True, null=True)
|
|
||||||
postcode = models.CharField(max_length=20, blank=True, null=True)
|
|
||||||
street = models.CharField(max_length=200, blank=True, null=True)
|
|
||||||
county = models.CharField(max_length=200, blank=True, null=True)
|
|
||||||
# Country code as per ISO 3166-1 alpha-2
|
|
||||||
# https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
|
|
||||||
countrycode = models.CharField(max_length=2, verbose_name=_("Ländercode"),
|
|
||||||
help_text=_("Standardisierter Ländercode nach ISO 3166-1 ALPHA-2"),
|
|
||||||
blank=True, null=True)
|
|
||||||
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)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.city and self.postcode:
|
return f"{self.name} ({self.latitude:.5}, {self.longitude:.5})"
|
||||||
return f"{self.city} ({self.postcode})"
|
|
||||||
else:
|
|
||||||
return f"{self.name} ({self.latitude:.5}, {self.longitude:.5})"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self):
|
def position(self):
|
||||||
@@ -87,11 +73,6 @@ class Location(models.Model):
|
|||||||
latitude=proxy.latitude,
|
latitude=proxy.latitude,
|
||||||
longitude=proxy.longitude,
|
longitude=proxy.longitude,
|
||||||
name=proxy.name,
|
name=proxy.name,
|
||||||
postcode=proxy.postcode,
|
|
||||||
city=proxy.city,
|
|
||||||
street=proxy.street,
|
|
||||||
county=proxy.county,
|
|
||||||
countrycode=proxy.countrycode,
|
|
||||||
)
|
)
|
||||||
return location
|
return location
|
||||||
|
|
||||||
@@ -103,33 +84,33 @@ class Location(models.Model):
|
|||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
class ImportantLocation(models.Model):
|
|
||||||
location = models.OneToOneField(Location, on_delete=models.CASCADE)
|
|
||||||
slug = models.SlugField(unique=True)
|
|
||||||
name = models.CharField(max_length=200)
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalSourceChoices(models.TextChoices):
|
class ExternalSourceChoices(models.TextChoices):
|
||||||
OSM = "OSM", _("Open Street Map")
|
OSM = "OSM", _("Open Street Map")
|
||||||
|
|
||||||
|
|
||||||
class AllowUseOfMaterialsChices(models.TextChoices):
|
|
||||||
USE_MATERIALS_ALLOWED = "allowed", _("Usage allowed")
|
|
||||||
USE_MATERIALS_REQUESTED = "requested", _("Usage requested")
|
|
||||||
USE_MATERIALS_DENIED = "denied", _("Usage denied")
|
|
||||||
USE_MATERIALS_OTHER = "other", _("It's complicated")
|
|
||||||
USE_MATERIALS_NOT_ASKED = "not_asked", _("Not asked")
|
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganization(models.Model):
|
class RescueOrganization(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}"
|
return f"{self.name}"
|
||||||
|
|
||||||
|
USE_MATERIALS_ALLOWED = "allowed"
|
||||||
|
USE_MATERIALS_REQUESTED = "requested"
|
||||||
|
USE_MATERIALS_DENIED = "denied"
|
||||||
|
USE_MATERIALS_OTHER = "other"
|
||||||
|
USE_MATERIALS_NOT_ASKED = "not_asked"
|
||||||
|
|
||||||
|
ALLOW_USE_MATERIALS_CHOICE = {
|
||||||
|
USE_MATERIALS_ALLOWED: "Usage allowed",
|
||||||
|
USE_MATERIALS_REQUESTED: "Usage requested",
|
||||||
|
USE_MATERIALS_DENIED: "Usage denied",
|
||||||
|
USE_MATERIALS_OTHER: "It's complicated",
|
||||||
|
USE_MATERIALS_NOT_ASKED: "Not asked"
|
||||||
|
}
|
||||||
|
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
trusted = models.BooleanField(default=False, verbose_name=_('Vertrauenswürdig'))
|
trusted = models.BooleanField(default=False, verbose_name=_('Vertrauenswürdig'))
|
||||||
allows_using_materials = models.CharField(max_length=200,
|
allows_using_materials = models.CharField(max_length=200,
|
||||||
default=AllowUseOfMaterialsChices.USE_MATERIALS_NOT_ASKED,
|
default=ALLOW_USE_MATERIALS_CHOICE[USE_MATERIALS_NOT_ASKED],
|
||||||
choices=AllowUseOfMaterialsChices.choices,
|
choices=ALLOW_USE_MATERIALS_CHOICE,
|
||||||
verbose_name=_('Erlaubt Nutzung von Inhalten'))
|
verbose_name=_('Erlaubt Nutzung von Inhalten'))
|
||||||
location_string = models.CharField(max_length=200, verbose_name=_("Ort der Organisation"))
|
location_string = models.CharField(max_length=200, verbose_name=_("Ort der Organisation"))
|
||||||
location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True)
|
location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True)
|
||||||
@@ -141,7 +122,6 @@ class RescueOrganization(models.Model):
|
|||||||
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)
|
||||||
last_checked = models.DateTimeField(auto_now_add=True, verbose_name=_('Datum der letzten Prüfung'))
|
|
||||||
internal_comment = models.TextField(verbose_name=_("Interner 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
|
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung')) # Markdown allowed
|
||||||
external_object_identifier = models.CharField(max_length=200, null=True, blank=True,
|
external_object_identifier = models.CharField(max_length=200, null=True, blank=True,
|
||||||
@@ -150,9 +130,6 @@ class RescueOrganization(models.Model):
|
|||||||
choices=ExternalSourceChoices.choices,
|
choices=ExternalSourceChoices.choices,
|
||||||
verbose_name=_('External Source Identifier'))
|
verbose_name=_('External Source Identifier'))
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ('external_object_identifier', 'external_source_identifier',)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("rescue-organization-detail", args=[str(self.pk)])
|
return reverse("rescue-organization-detail", args=[str(self.pk)])
|
||||||
|
|
||||||
@@ -172,20 +149,7 @@ class RescueOrganization(models.Model):
|
|||||||
if self.description is None:
|
if self.description is None:
|
||||||
return ""
|
return ""
|
||||||
if len(self.description) > 200:
|
if len(self.description) > 200:
|
||||||
return self.description[:200] + _(f" ... [weiterlesen]({self.get_absolute_url()})")
|
return self.description[:200] + f" ... [weiterlesen]({self.get_absolute_url()})"
|
||||||
|
|
||||||
def set_checked(self):
|
|
||||||
self.last_checked = timezone.now()
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def last_checked_hr(self):
|
|
||||||
time_since_last_checked = timezone.now() - self.last_checked
|
|
||||||
return time_since_as_hr_string(time_since_last_checked)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def species_urls(self):
|
|
||||||
return SpeciesSpecificURL.objects.filter(organization=self)
|
|
||||||
|
|
||||||
|
|
||||||
# Admins can perform all actions and have the highest trust associated with them
|
# Admins can perform all actions and have the highest trust associated with them
|
||||||
@@ -297,13 +261,11 @@ class AdoptionNotice(models.Model):
|
|||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
last_checked = models.DateTimeField(verbose_name=_('Zuletzt überprüft am'), default=timezone.now)
|
last_checked = models.DateTimeField(verbose_name=_('Zuletzt überprüft am'), default=timezone.now)
|
||||||
searching_since = models.DateField(verbose_name=_('Sucht nach einem Zuhause seit'))
|
searching_since = models.DateField(verbose_name=_('Sucht nach einem Zuhause seit'))
|
||||||
name = models.CharField(max_length=200, verbose_name=_('Titel der Vermittlung'))
|
name = models.CharField(max_length=200)
|
||||||
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
|
||||||
organization = models.ForeignKey(RescueOrganization, blank=True, null=True, on_delete=models.SET_NULL,
|
organization = models.ForeignKey(RescueOrganization, blank=True, null=True, on_delete=models.SET_NULL,
|
||||||
verbose_name=_('Organisation'))
|
verbose_name=_('Organisation'))
|
||||||
further_information = models.URLField(null=True, blank=True,
|
further_information = models.URLField(null=True, blank=True, verbose_name=_('Link zu mehr Informationen'))
|
||||||
verbose_name=_('Link zu mehr Informationen'),
|
|
||||||
help_text=_("Verlinke hier die Quelle der Vermittlung (z.B. die Website des Tierheims"))
|
|
||||||
group_only = models.BooleanField(default=False, verbose_name=_('Ausschließlich Gruppenadoption'))
|
group_only = models.BooleanField(default=False, verbose_name=_('Ausschließlich Gruppenadoption'))
|
||||||
photos = models.ManyToManyField(Image, blank=True)
|
photos = models.ManyToManyField(Image, blank=True)
|
||||||
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
|
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
|
||||||
@@ -321,13 +283,6 @@ class AdoptionNotice(models.Model):
|
|||||||
sexes.add(animal.sex)
|
sexes.add(animal.sex)
|
||||||
return sexes
|
return sexes
|
||||||
|
|
||||||
@property
|
|
||||||
def num_per_sex(self):
|
|
||||||
num_per_sex = dict()
|
|
||||||
for sex in SexChoices:
|
|
||||||
num_per_sex[sex] = self.animals.filter(sex=sex).count
|
|
||||||
return num_per_sex
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_checked_hr(self):
|
def last_checked_hr(self):
|
||||||
time_since_last_checked = timezone.now() - self.last_checked
|
time_since_last_checked = timezone.now() - self.last_checked
|
||||||
@@ -368,10 +323,6 @@ class AdoptionNotice(models.Model):
|
|||||||
"""Returns the url to access a detailed page for the adoption notice."""
|
"""Returns the url to access a detailed page for the adoption notice."""
|
||||||
return reverse('adoption-notice-detail', args=[str(self.id)])
|
return reverse('adoption-notice-detail', args=[str(self.id)])
|
||||||
|
|
||||||
def get_absolute_url_bulma(self):
|
|
||||||
"""Returns the url to access a detailed page for the adoption notice."""
|
|
||||||
return reverse('adoption-notice-detail-bulma', args=[str(self.id)])
|
|
||||||
|
|
||||||
def get_report_url(self):
|
def get_report_url(self):
|
||||||
"""Returns the url to report an adoption notice."""
|
"""Returns the url to report an adoption notice."""
|
||||||
return reverse('report-adoption-notice', args=[str(self.id)])
|
return reverse('report-adoption-notice', args=[str(self.id)])
|
||||||
@@ -715,31 +666,6 @@ class Report(models.Model):
|
|||||||
def get_moderation_actions(self):
|
def get_moderation_actions(self):
|
||||||
return ModerationAction.objects.filter(report=self)
|
return ModerationAction.objects.filter(report=self)
|
||||||
|
|
||||||
@property
|
|
||||||
def reported_content(self):
|
|
||||||
"""
|
|
||||||
Dynamically fetch the reported content based on subclass.
|
|
||||||
The alternative would be to use the ContentType framework:
|
|
||||||
https://docs.djangoproject.com/en/5.1/ref/contrib/contenttypes/
|
|
||||||
"""
|
|
||||||
if hasattr(self, "reportadoptionnotice"):
|
|
||||||
return self.reportadoptionnotice.adoption_notice
|
|
||||||
elif hasattr(self, "reportcomment"):
|
|
||||||
return self.reportcomment.reported_comment
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def reported_content_url(self):
|
|
||||||
"""
|
|
||||||
Same as reported_content, just for url
|
|
||||||
"""
|
|
||||||
if hasattr(self, "reportadoptionnotice"):
|
|
||||||
print(self.reportadoptionnotice.adoption_notice.get_absolute_url)
|
|
||||||
return self.reportadoptionnotice.adoption_notice.get_absolute_url
|
|
||||||
elif hasattr(self, "reportcomment"):
|
|
||||||
return self.reportcomment.reported_comment.get_absolute_url
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class ReportAdoptionNotice(Report):
|
class ReportAdoptionNotice(Report):
|
||||||
adoption_notice = models.ForeignKey("AdoptionNotice", on_delete=models.CASCADE)
|
adoption_notice = models.ForeignKey("AdoptionNotice", on_delete=models.CASCADE)
|
||||||
@@ -748,9 +674,6 @@ class ReportAdoptionNotice(Report):
|
|||||||
def reported_content(self):
|
def reported_content(self):
|
||||||
return self.adoption_notice
|
return self.adoption_notice
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Report der Vermittlung {self.adoption_notice}"
|
|
||||||
|
|
||||||
|
|
||||||
class ReportComment(Report):
|
class ReportComment(Report):
|
||||||
reported_comment = models.ForeignKey("Comment", on_delete=models.CASCADE)
|
reported_comment = models.ForeignKey("Comment", on_delete=models.CASCADE)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
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 fellchensammlung.models import BaseNotification, CommentNotification, User, TrustLevel, RescueOrganization
|
from fellchensammlung.models import BaseNotification, CommentNotification, User, TrustLevel
|
||||||
from .tasks import task_send_notification_email
|
from .tasks import task_send_notification_email
|
||||||
from notfellchen.settings import host
|
from notfellchen.settings import host
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -18,13 +18,6 @@ def base_notification_receiver(sender, instance: BaseNotification, created: bool
|
|||||||
else:
|
else:
|
||||||
task_send_notification_email.delay(instance.pk)
|
task_send_notification_email.delay(instance.pk)
|
||||||
|
|
||||||
@receiver(post_save, sender=RescueOrganization)
|
|
||||||
def rescue_org_receiver(sender, instance: RescueOrganization, created: bool, **kwargs):
|
|
||||||
if instance.location:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
task_send_notification_email.delay(instance.pk)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def notification_new_user(sender, instance: User, created: bool, **kwargs):
|
def notification_new_user(sender, instance: User, created: bool, **kwargs):
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
from django.contrib.sitemaps import Sitemap
|
|
||||||
from django.urls import reverse
|
|
||||||
from .models import AdoptionNotice, RescueOrganization
|
|
||||||
|
|
||||||
|
|
||||||
class StaticViewSitemap(Sitemap):
|
|
||||||
priority = 0.8
|
|
||||||
changefreq = "weekly"
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return ["index", "search", "map", "about", "rescue-organizations"]
|
|
||||||
|
|
||||||
def location(self, item):
|
|
||||||
return reverse(item)
|
|
||||||
|
|
||||||
|
|
||||||
class AdoptionNoticeSitemap(Sitemap):
|
|
||||||
priority = 0.5
|
|
||||||
changefreq = "daily"
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return AdoptionNotice.get_active_ANs()
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.updated_at
|
|
||||||
|
|
||||||
|
|
||||||
class AnimalSitemap(Sitemap):
|
|
||||||
priority = 0.2
|
|
||||||
changefreq = "daily"
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return AdoptionNotice.objects.all()
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.updated_at
|
|
||||||
|
|
||||||
|
|
||||||
class RescueOrganizationSitemap(Sitemap):
|
|
||||||
priority = 0.3
|
|
||||||
changefreq = "weekly"
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return RescueOrganization.objects.all()
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.updated_at
|
|
@@ -1,60 +0,0 @@
|
|||||||
/***************/
|
|
||||||
/* MAIN COLORS */
|
|
||||||
/***************/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--primary-light-one: #5daa68;
|
|
||||||
--primary-light-two: #4a9455;
|
|
||||||
--primary-semidark-one: #356c3c;
|
|
||||||
--primary-dark-one: #17311b;
|
|
||||||
--secondary-light-one: #faf1cf;
|
|
||||||
--secondary-light-two: #e1d7b5;
|
|
||||||
--background-one: var(--primary-light-one);
|
|
||||||
--background-two: var(--primary-light-two);
|
|
||||||
--background-three: var(--secondary-light-one);
|
|
||||||
--background-four: var(--primary-dark-one);
|
|
||||||
--highlight-one: var(--primary-dark-one);
|
|
||||||
--highlight-one-text: var(--secondary-light-one);
|
|
||||||
--highlight-two: var(--primary-semidark-one);
|
|
||||||
--text-one: var(--secondary-light-one);
|
|
||||||
--shadow-one: var(--primary-dark-one);
|
|
||||||
--text-two: var(--primary-dark-one);
|
|
||||||
--text-three: var(--primary-light-one);
|
|
||||||
--shadow-three: var(--primary-dark-one);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******/
|
|
||||||
/* MAP */
|
|
||||||
/*******/
|
|
||||||
|
|
||||||
.map {
|
|
||||||
border-radius: 8px;
|
|
||||||
width:100%;
|
|
||||||
height:100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.marker {
|
|
||||||
background-image: url('../img/logo_transparent.png');
|
|
||||||
background-size: cover;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animal-shelter-marker {
|
|
||||||
background-image: url('../img/animal_shelter.png');
|
|
||||||
!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maplibregl-popup {
|
|
||||||
max-width: 600px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-in-content #map {
|
|
||||||
max-height: 500px;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
@@ -1,420 +0,0 @@
|
|||||||
/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */
|
|
||||||
|
|
||||||
.pswp {
|
|
||||||
--pswp-bg: #000;
|
|
||||||
--pswp-placeholder-bg: #222;
|
|
||||||
|
|
||||||
|
|
||||||
--pswp-root-z-index: 100000;
|
|
||||||
|
|
||||||
--pswp-preloader-color: rgba(79, 79, 79, 0.4);
|
|
||||||
--pswp-preloader-color-secondary: rgba(255, 255, 255, 0.9);
|
|
||||||
|
|
||||||
/* defined via js:
|
|
||||||
--pswp-transition-duration: 333ms; */
|
|
||||||
|
|
||||||
--pswp-icon-color: #fff;
|
|
||||||
--pswp-icon-color-secondary: #4f4f4f;
|
|
||||||
--pswp-icon-stroke-color: #4f4f4f;
|
|
||||||
--pswp-icon-stroke-width: 2px;
|
|
||||||
|
|
||||||
--pswp-error-text-color: var(--pswp-icon-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Styles for basic PhotoSwipe (pswp) functionality (sliding area, open/close transitions)
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pswp {
|
|
||||||
position: fixed;
|
|
||||||
z-index: var(--pswp-root-z-index);
|
|
||||||
display: none;
|
|
||||||
touch-action: none;
|
|
||||||
outline: 0;
|
|
||||||
opacity: 0.003;
|
|
||||||
contain: layout style size;
|
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevents focus outline on the root element,
|
|
||||||
(it may be focused initially) */
|
|
||||||
.pswp:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp * {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp img {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--open {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp,
|
|
||||||
.pswp__bg {
|
|
||||||
transform: translateZ(0);
|
|
||||||
will-change: opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__bg {
|
|
||||||
opacity: 0.005;
|
|
||||||
background: var(--pswp-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp,
|
|
||||||
.pswp__scroll-wrap {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp,
|
|
||||||
.pswp__scroll-wrap,
|
|
||||||
.pswp__bg,
|
|
||||||
.pswp__container,
|
|
||||||
.pswp__item,
|
|
||||||
.pswp__content,
|
|
||||||
.pswp__img,
|
|
||||||
.pswp__zoom-wrap {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp {
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__img,
|
|
||||||
.pswp__zoom-wrap {
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img {
|
|
||||||
cursor: -webkit-zoom-in;
|
|
||||||
cursor: -moz-zoom-in;
|
|
||||||
cursor: zoom-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--click-to-zoom.pswp--zoomed-in .pswp__img {
|
|
||||||
cursor: move;
|
|
||||||
cursor: -webkit-grab;
|
|
||||||
cursor: -moz-grab;
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active {
|
|
||||||
cursor: -webkit-grabbing;
|
|
||||||
cursor: -moz-grabbing;
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* :active to override grabbing cursor */
|
|
||||||
.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,
|
|
||||||
.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,
|
|
||||||
.pswp__img {
|
|
||||||
cursor: -webkit-zoom-out;
|
|
||||||
cursor: -moz-zoom-out;
|
|
||||||
cursor: zoom-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Prevent selection and tap highlights */
|
|
||||||
.pswp__container,
|
|
||||||
.pswp__img,
|
|
||||||
.pswp__button,
|
|
||||||
.pswp__counter {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__item {
|
|
||||||
/* z-index for fade transition */
|
|
||||||
z-index: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allow to click through pswp__content element, but not its children */
|
|
||||||
.pswp__content {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.pswp__content > * {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
PhotoSwipe UI
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Error message appears when image is not loaded
|
|
||||||
(JS option errorMsg controls markup)
|
|
||||||
*/
|
|
||||||
.pswp__error-msg-container {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
.pswp__error-msg {
|
|
||||||
margin: auto;
|
|
||||||
font-size: 1em;
|
|
||||||
line-height: 1;
|
|
||||||
color: var(--pswp-error-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
class pswp__hide-on-close is applied to elements that
|
|
||||||
should hide (for example fade out) when PhotoSwipe is closed
|
|
||||||
and show (for example fade in) when PhotoSwipe is opened
|
|
||||||
*/
|
|
||||||
.pswp .pswp__hide-on-close {
|
|
||||||
opacity: 0.005;
|
|
||||||
will-change: opacity;
|
|
||||||
transition: opacity var(--pswp-transition-duration) cubic-bezier(0.4, 0, 0.22, 1);
|
|
||||||
z-index: 10; /* always overlap slide content */
|
|
||||||
pointer-events: none; /* hidden elements should not be clickable */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* class pswp--ui-visible is added when opening or closing transition starts */
|
|
||||||
.pswp--ui-visible .pswp__hide-on-close {
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* <button> styles, including css reset */
|
|
||||||
.pswp__button {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
width: 50px;
|
|
||||||
height: 60px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
opacity: 0.85;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__button:hover,
|
|
||||||
.pswp__button:active,
|
|
||||||
.pswp__button:focus {
|
|
||||||
transition: none;
|
|
||||||
padding: 0;
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__button:disabled {
|
|
||||||
opacity: 0.3;
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__icn {
|
|
||||||
fill: var(--pswp-icon-color);
|
|
||||||
color: var(--pswp-icon-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__icn {
|
|
||||||
position: absolute;
|
|
||||||
top: 14px;
|
|
||||||
left: 9px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__icn-shadow {
|
|
||||||
stroke: var(--pswp-icon-stroke-color);
|
|
||||||
stroke-width: var(--pswp-icon-stroke-width);
|
|
||||||
fill: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__icn:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
div element that matches size of large image,
|
|
||||||
large image loads on top of it,
|
|
||||||
used when msrc is not provided
|
|
||||||
*/
|
|
||||||
div.pswp__img--placeholder,
|
|
||||||
.pswp__img--with-bg {
|
|
||||||
background: var(--pswp-placeholder-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__top-bar {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 60px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
/* allow events to pass through top bar itself */
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
.pswp__top-bar > * {
|
|
||||||
pointer-events: auto;
|
|
||||||
/* this makes transition significantly more smooth,
|
|
||||||
even though inner elements are not animated */
|
|
||||||
will-change: opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Close button
|
|
||||||
|
|
||||||
*/
|
|
||||||
.pswp__button--close {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Arrow buttons
|
|
||||||
|
|
||||||
*/
|
|
||||||
.pswp__button--arrow {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 75px;
|
|
||||||
height: 100px;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__button--arrow:disabled {
|
|
||||||
display: none;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__button--arrow .pswp__icn {
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -30px;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
background: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--one-slide .pswp__button--arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hide arrows on touch screens */
|
|
||||||
.pswp--touch .pswp__button--arrow {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* show arrows only after mouse was used */
|
|
||||||
.pswp--has_mouse .pswp__button--arrow {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__button--arrow--prev {
|
|
||||||
right: auto;
|
|
||||||
left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__button--arrow--next {
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
.pswp__button--arrow--next .pswp__icn {
|
|
||||||
left: auto;
|
|
||||||
right: 14px;
|
|
||||||
/* flip horizontally */
|
|
||||||
transform: scale(-1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Zoom button
|
|
||||||
|
|
||||||
*/
|
|
||||||
.pswp__button--zoom {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--zoom-allowed .pswp__button--zoom {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* "+" => "-" */
|
|
||||||
.pswp--zoomed-in .pswp__zoom-icn-bar-v {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Loading indicator
|
|
||||||
|
|
||||||
*/
|
|
||||||
.pswp__preloader {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 50px;
|
|
||||||
height: 60px;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__preloader .pswp__icn {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s linear;
|
|
||||||
animation: pswp-clockwise 600ms linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp__preloader--active .pswp__icn {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pswp-clockwise {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
"1 of 10" counter
|
|
||||||
|
|
||||||
*/
|
|
||||||
.pswp__counter {
|
|
||||||
height: 30px;
|
|
||||||
margin: 15px 0 0 20px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 30px;
|
|
||||||
color: var(--pswp-icon-color);
|
|
||||||
text-shadow: 1px 1px 3px var(--pswp-icon-color-secondary);
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pswp--one-slide .pswp__counter {
|
|
||||||
display: none;
|
|
||||||
}
|
|
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB |
@@ -1,11 +0,0 @@
|
|||||||
import PhotoSwipeLightbox from 'https://unpkg.com/photoswipe/dist/photoswipe-lightbox.esm.js';
|
|
||||||
|
|
||||||
const lightbox = new PhotoSwipeLightbox({
|
|
||||||
gallery: '#my-gallery',
|
|
||||||
children: 'a',
|
|
||||||
pswpModule: () => import('https://unpkg.com/photoswipe'),
|
|
||||||
});
|
|
||||||
|
|
||||||
lightbox.init();
|
|
||||||
|
|
||||||
|
|
@@ -1,32 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
|
|
||||||
// Get all "navbar-burger" elements
|
|
||||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
|
||||||
|
|
||||||
// Add a click event on each of them
|
|
||||||
$navbarBurgers.forEach( el => {
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
|
|
||||||
// Get the target from the "data-target" attribute
|
|
||||||
const target = el.dataset.target;
|
|
||||||
const $target = document.getElementById(target);
|
|
||||||
|
|
||||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
|
||||||
el.classList.toggle('is-active');
|
|
||||||
$target.classList.toggle('is-active');
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Looks for all notifications with a delete and allows closing them when pressing delete
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
|
|
||||||
const $notification = $delete.parentNode;
|
|
||||||
|
|
||||||
$delete.addEventListener('click', () => {
|
|
||||||
$notification.parentNode.removeChild($notification);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,7 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow: /admin/
|
|
||||||
|
|
||||||
User-agent: OpenAI
|
|
||||||
Disallow: /
|
|
||||||
|
|
||||||
Sitemap: https://notfellchen.org/sitemap.xml
|
|
@@ -6,7 +6,7 @@ from notfellchen.celery import app as celery_app
|
|||||||
from .mail import send_notification_email
|
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, RescueOrganization
|
from .models import Location, AdoptionNotice, Timestamp
|
||||||
from .tools.notifications import notify_of_AN_to_be_checked
|
from .tools.notifications import notify_of_AN_to_be_checked
|
||||||
from .tools.search import notify_search_subscribers
|
from .tools.search import notify_search_subscribers
|
||||||
|
|
||||||
@@ -57,10 +57,3 @@ def task_healthcheck():
|
|||||||
@shared_task
|
@shared_task
|
||||||
def task_send_notification_email(notification_pk):
|
def task_send_notification_email(notification_pk):
|
||||||
send_notification_email(notification_pk)
|
send_notification_email(notification_pk)
|
||||||
|
|
||||||
@celery_app.task(name="commit.post_rescue_org_save")
|
|
||||||
def post_rescue_org_save(pk):
|
|
||||||
instance = RescueOrganization.objects.get(pk=pk)
|
|
||||||
Location.add_location_to_object(instance)
|
|
||||||
set_timestamp("add_rescue_org_location")
|
|
||||||
logging.info(f"Location was added to Rescue Organization {pk}")
|
|
@@ -1,43 +0,0 @@
|
|||||||
{% load custom_tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
{% get_current_language as LANGUAGE_CODE%}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ LANGUAGE_CODE }}">
|
|
||||||
<head>
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="{% translate "Farbratten aus dem Tierschutz finden und adoptieren" %}">
|
|
||||||
<!-- Add additional CSS in static file -->
|
|
||||||
<link rel="stylesheet" href="{% static 'fellchensammlung/css/bulma-styles.css' %}">
|
|
||||||
<link rel="stylesheet" href="{% static 'fellchensammlung/css/bulma.min.css' %}">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/photoswipe@5.2.2/dist/photoswipe.css">
|
|
||||||
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
|
|
||||||
<link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet" type="text/css">
|
|
||||||
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
|
|
||||||
|
|
||||||
<script src="{% static 'fellchensammlung/js/custom.js' %}"></script>
|
|
||||||
<script src="{% static 'fellchensammlung/js/toggles.js' %}"></script>
|
|
||||||
<script type="module" src="{% static 'fellchensammlung/js/photoswipe.js' %}"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'fellchensammlung/favicon/apple-touch-icon.png' %}">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'fellchensammlung/favicon/favicon-32x32.png' %}">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'fellchensammlung/favicon/favicon-16x16.png' %}">
|
|
||||||
{% get_oxitraffic_script_if_enabled %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% block header %}
|
|
||||||
{% include "fellchensammlung/bulma-header.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
<div class="content">
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{% block footer %}
|
|
||||||
{% include "fellchensammlung/bulma-footer.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,8 +1,7 @@
|
|||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% get_current_language as LANGUAGE_CODE%}
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ LANGUAGE_CODE }}">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
{% block title %}{% endblock %}
|
{% block title %}{% endblock %}
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
|
|
||||||
{% block title %}<title>{% translate "Über uns" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% if about_us %}
|
|
||||||
<div class="block">
|
|
||||||
<h1 class="title is-1">{{ about_us.title }}</h1>
|
|
||||||
<div class="content">
|
|
||||||
{{ about_us.content | render_markdown }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if faq %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2 class="card-header-title">{{ faq.title }}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
{{ faq.content | render_markdown }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@@ -1,83 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
<footer class="footer">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column">
|
|
||||||
<div class="block">
|
|
||||||
<h3 class="bd-footer-title title is-3 has-text-left">
|
|
||||||
Notfellchen
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<!-- footer content -->
|
|
||||||
<p class="bd-footer-link
|
|
||||||
has-text-left">
|
|
||||||
Für Menschen die Ratten aus dem Tierschutz ein liebendes Zuhause geben wollen.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
<h3 class="bd-footer-title title is-5">
|
|
||||||
{% trans 'Sprache ändern' %}
|
|
||||||
</h3>
|
|
||||||
{% include "fellchensammlung/forms/change_language.html" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
<h4 class="bd-footer-title title is-4 has-text-justify">
|
|
||||||
{% translate 'Über uns' %}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<a class="bd-footer-link" href="{% url "about-bulma" %}">
|
|
||||||
{% translate 'Das Notfellchen Projekt' %}
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
<a class="bd-footer-link" href="{% url "terms-of-service" %}">
|
|
||||||
{% translate 'Nutzungsbedingungen' %}
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<a class="bd-footer-link" href="{% url "privacy" %}">
|
|
||||||
{% translate 'Datenschutz' %}
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
<a class="bd-footer-link" href="{% url "imprint" %}">
|
|
||||||
{% translate 'Impressum' %}
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
<h4 class="bd-footer-title title is-4 has-text-justify">
|
|
||||||
Technisches
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<p class="bd-footer-link">
|
|
||||||
<a class="nav-link " href="{% url "rss" %}">
|
|
||||||
<i class="fa-solid fa-rss"></i> {% translate 'RSS' %}
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<a href="https://dokumentation.notfellchen.org/">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span>{% translate 'Dokumentation' %}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<a href="mailto:info@notfellchen.org">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span>{% translate 'Probleme melden' %}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<br/>
|
|
||||||
<a href="https://codeberg.org/moanos/notfellchen">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span>{% trans 'Code' %}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
@@ -1,43 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
|
|
||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<a class="navbar-item" href="{% url 'index-bulma' %}">
|
|
||||||
<img src="{% static 'fellchensammlung/img/logo_transparent.png' %}" alt="{% trans 'Notfellchen Logo' %}">
|
|
||||||
<h1 class="title is-4">notfellchen.org</h1>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navbarBasicExample" class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item" href="{% url 'search-bulma' %}">
|
|
||||||
<i class="fas fa-search"></i> {% translate 'Suchen' %}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="navbar-item" href="{% url "add-adoption-bulma" %}">
|
|
||||||
<i class="fas fa-feather"></i> {% translate 'Vermittlung hinzufügen' %}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="navbar-end">
|
|
||||||
<div class="navbar-item">
|
|
||||||
<div class="buttons">
|
|
||||||
<a class="button is-primary" href="{% url "django_registration_register" %}">
|
|
||||||
<strong>{% translate "Registrieren" %}</strong>
|
|
||||||
</a>
|
|
||||||
<a class="button is-light" href="{% url "login" %}">
|
|
||||||
<strong>{% translate "Login" %}</strong>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
@@ -1,34 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
|
|
||||||
{% block title %}<title>{% translate "Notfellchen - Farbratten aus dem Tierschutz adoptieren" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% for announcement in announcements %}
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-announcement.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if introduction %}
|
|
||||||
<h1>{{ introduction.title }}</h1>
|
|
||||||
{{ introduction.content | render_markdown }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h2>{% translate "Aktuelle Vermittlungen" %}</h2>
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
{% include "fellchensammlung/lists/bulma-list-adoption-notices.html" %}
|
|
||||||
<a class="button is-primary" href="{% url 'search' %}">{% translate "Mehr Vermittlungen" %}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block" style="height: 50vh">
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-map.html" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if how_to %}
|
|
||||||
<div class="card">
|
|
||||||
<h1>{{ how_to.title }}</h1>
|
|
||||||
{{ how_to.content | render_markdown }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@@ -1,9 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}<title>{% translate "Karte" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div style="height:70vh">
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-map.html" %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@@ -1,15 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
|
|
||||||
{% block title %}<title>{{ text.title }}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="block">
|
|
||||||
<h1 class="title is-1">{{ text.title }}</h1>
|
|
||||||
<div class="content">
|
|
||||||
{{ text.content | render_markdown }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@@ -1,97 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}<title>{% translate "Suche" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% get_current_language as LANGUAGE_CODE_CURRENT %}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-two-thirds">
|
|
||||||
<div style="height: 50vh">
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-map.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<form class="block" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="longitude" maxlength="200" id="longitude">
|
|
||||||
<input type="hidden" name="latitude" maxlength="200" id="latitude">
|
|
||||||
<input type="hidden" id="place_id" name="place_id">
|
|
||||||
<!--- https://docs.djangoproject.com/en/5.2/topics/forms/#reusable-form-templates -->
|
|
||||||
{{ search_form }}
|
|
||||||
<ul id="results"></ul>
|
|
||||||
<button class="button is-primary" type="submit" value="search" name="search">
|
|
||||||
<i class="fas fa-search"></i> {% trans 'Suchen' %}
|
|
||||||
</button>
|
|
||||||
{% if searched %}
|
|
||||||
{% if subscribed_search %}
|
|
||||||
<button class="button" type="submit" value="{{ subscribed_search.pk }}"
|
|
||||||
name="unsubscribe_to_search">
|
|
||||||
<i class="fas fa-bell-slash"></i> {% trans 'Suche nicht mehr abonnieren' %}
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<button class="button" type="submit" name="subscribe_to_search">
|
|
||||||
<i class="fas fa-bell"></i> {% trans 'Suche abonnieren' %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
<div class="block">
|
|
||||||
{% if place_not_found %}
|
|
||||||
<div class="block notification is-warning">
|
|
||||||
<p>
|
|
||||||
{% trans 'Ort nicht gefunden' %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="">
|
|
||||||
{% include "fellchensammlung/lists/bulma-list-adoption-notices.html" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const locationInput = document.getElementById('id_location_string');
|
|
||||||
const resultsList = document.getElementById('results');
|
|
||||||
const placeIdInput = document.getElementById('place_id');
|
|
||||||
|
|
||||||
locationInput.addEventListener('input', async function () {
|
|
||||||
const query = locationInput.value.trim();
|
|
||||||
|
|
||||||
if (query.length < 3) {
|
|
||||||
resultsList.innerHTML = ''; // Don't search for or show results if input is less than 3 characters
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`{{ geocoding_api_url }}/?q=${encodeURIComponent(query)}&limit=5&lang={{ LANGUAGE_CODE_CURRENT }}`);
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data && data.features) {
|
|
||||||
resultsList.innerHTML = ''; // Clear previous results
|
|
||||||
|
|
||||||
const locations = data.features.slice(0, 5); // Show only the first 5 results
|
|
||||||
|
|
||||||
locations.forEach(location => {
|
|
||||||
const listItem = document.createElement('li');
|
|
||||||
listItem.classList.add('result-item');
|
|
||||||
listItem.textContent = geojson_to_summary(location);
|
|
||||||
|
|
||||||
// Add event when user clicks on a result location
|
|
||||||
listItem.addEventListener('click', () => {
|
|
||||||
|
|
||||||
locationInput.value = geojson_to_searchable_string(location); // Set input field to selected location
|
|
||||||
resultsList.innerHTML = ''; // Clear the results after selecting a location
|
|
||||||
});
|
|
||||||
|
|
||||||
resultsList.appendChild(listItem);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching location data:', error);
|
|
||||||
resultsList.innerHTML = '<li class="result-item">Error fetching data. Please try again.</li>';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@@ -1,120 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
{% block title %}<title>{% translate "Styleguide für Bulma" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="section">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title">
|
|
||||||
Hello World
|
|
||||||
</h1>
|
|
||||||
<p class="subtitle">
|
|
||||||
Notfellchen bald mit <strong>Bulma</strong>?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<img
|
|
||||||
src="{% static 'fellchensammlung/img/example_rat_single.png' %}"
|
|
||||||
alt="Placeholder image"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img
|
|
||||||
src="https://bulma.io/assets/images/placeholders/96x96.png"
|
|
||||||
alt="Placeholder image"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">John Smith</p>
|
|
||||||
<p class="subtitle is-6">@johnsmith</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
Süße Ratte sucht Zuhause
|
|
||||||
<a href="#">#responsive</a>
|
|
||||||
<br/>
|
|
||||||
<time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<img
|
|
||||||
src="{% static 'fellchensammlung/img/example_rat_single.png' %}"
|
|
||||||
alt="Placeholder image"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img
|
|
||||||
src="https://bulma.io/assets/images/placeholders/96x96.png"
|
|
||||||
alt="Placeholder image"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">John Smith</p>
|
|
||||||
<p class="subtitle is-6">@johnsmith</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
Süßeste Ratte sucht Zuhause
|
|
||||||
<a href="#">#responsive</a>
|
|
||||||
<br/>
|
|
||||||
<time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-image">
|
|
||||||
<figure class="image">
|
|
||||||
<img
|
|
||||||
src="{% static 'fellchensammlung/img/example_rat_single.png' %}"
|
|
||||||
alt="Placeholder image"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img
|
|
||||||
src="https://bulma.io/assets/images/placeholders/96x96.png"
|
|
||||||
alt="Placeholder image"
|
|
||||||
/>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">John Smith</p>
|
|
||||||
<p class="subtitle is-6">@johnsmith</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
Süßere Ratte sucht Zuhause
|
|
||||||
<a href="#">#responsive</a>
|
|
||||||
<br/>
|
|
||||||
<time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
@@ -1,17 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
|
|
||||||
{% block title %}<title>{% translate "Über uns" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2 class="title is-2">{% translate "Regeln" %}</h2>
|
|
||||||
{% include "fellchensammlung/lists/bulma-list-rules.html" %}
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
<h1 class="title is-1">{{ text.title }}</h1>
|
|
||||||
<div class="content">
|
|
||||||
{{ text.content | render_markdown }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@@ -1,113 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}<title>{{ adoption_notice.name }}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h1 class="card-header-title title is-2">{{ adoption_notice.name }}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="grid">
|
|
||||||
<div class="cell">
|
|
||||||
<!--- General Information --->
|
|
||||||
<div class="grid">
|
|
||||||
|
|
||||||
<div class="cell">
|
|
||||||
<h2><strong>{% translate "Ort" %}</strong></h2>
|
|
||||||
<p>{% if adoption_notice.location %}
|
|
||||||
{{ adoption_notice.location }}
|
|
||||||
{% else %}
|
|
||||||
{{ adoption_notice.location_string }}
|
|
||||||
{% endif %}</p>
|
|
||||||
</div>
|
|
||||||
<div class="cell">
|
|
||||||
{% include "fellchensammlung/partials/bulma-sex-overview.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="columns">
|
|
||||||
<!--- Images --->
|
|
||||||
<div class="column block">
|
|
||||||
<div class="card">
|
|
||||||
<div class="grid card-content">
|
|
||||||
<div class="cell" id="my-gallery">
|
|
||||||
{% for photo in adoption_notice.get_photos %}
|
|
||||||
<a href="{{ MEDIA_URL }}/{{ photo.image }}"
|
|
||||||
data-pswp-width="{{ photo.image.width }}"
|
|
||||||
data-pswp-height="{{ photo.image.height }}"
|
|
||||||
target="_blank">
|
|
||||||
<img style="height: 12rem" src="{{ MEDIA_URL }}/{{ photo.image }}"
|
|
||||||
alt="{ photo.alt_text }}"/>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--- Description --->
|
|
||||||
<div class="column block">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h1 class="card-header-title title is-2">{% translate "Beschreibung" %}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="expandable">{% if adoption_notice.description %}
|
|
||||||
{{ adoption_notice.description | render_markdown }}
|
|
||||||
{% else %}
|
|
||||||
{% translate "Keine Beschreibung angegeben" %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
{% if has_edit_permission %}
|
|
||||||
<div class="card-footer-item">
|
|
||||||
<div class="column">
|
|
||||||
<a class="button is-primary is-light"
|
|
||||||
href="{% url 'adoption-notice-add-photo' adoption_notice_id=adoption_notice.pk %}">
|
|
||||||
{% translate 'Foto hinzufügen' %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer-item">
|
|
||||||
<a class="button is-primary"
|
|
||||||
href="{% url 'adoption-notice-edit' adoption_notice_id=adoption_notice.pk %}">
|
|
||||||
{% translate 'Bearbeiten' %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="columns">
|
|
||||||
{% for animal in adoption_notice.animals %}
|
|
||||||
<div class="column">
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-animal-card.html" %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
{% if adoption_notice.further_information %}
|
|
||||||
<form method="get" action="{% url 'external-site' %}">
|
|
||||||
<input type="hidden" name="url" value="{{ adoption_notice.further_information }}">
|
|
||||||
<button class="button is-primary is-fullwidth" type="submit" id="submit">
|
|
||||||
{{ adoption_notice.further_information | domain }} <i
|
|
||||||
class="fa-solid fa-arrow-up-right-from-square"></i>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-comment-section.html" %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@@ -1,95 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_bulma.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
{% load widget_tweaks %}
|
|
||||||
|
|
||||||
{% block title %}<title>{% translate "Vermittlung hinzufügen" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% translate "Vermitteln" %}</h1>
|
|
||||||
<div class="notification">
|
|
||||||
<button class="delete"></button>
|
|
||||||
<p>
|
|
||||||
{% url 'terms-of-service' as rules_url %}
|
|
||||||
{% trans "Regeln" as rules_text %}
|
|
||||||
{% blocktranslate with rules_link='<a href="'|add:rules_url|add:'">'|add:rules_text|add:'</a>'|safe %}
|
|
||||||
Bitte mach dich zunächst mit unseren {{ rules_link }} vertraut. Dann trage hier die ersten Informationen
|
|
||||||
ein.
|
|
||||||
Fotos kannst du im nächsten Schritt hinzufügen.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-name">{{ form.name.label }}
|
|
||||||
{% if form.name.field.required %}<span class="special_class">*</span>{% endif %}</label>
|
|
||||||
{{ form.name|add_class:"input"|attr:"id:an-name" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-description">{% translate 'Beschreibung' %}</label>
|
|
||||||
{{ form.description|add_class:"input textarea"|attr:"rows:3"|attr:"id:an-description" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-location">{{ form.location_string.label }}</label>
|
|
||||||
{{ form.location_string|add_class:"input"|attr:"id:an-location" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="checkbox" for="an-group-only">{{ form.group_only.label }}</label>
|
|
||||||
{{ form.group_only|add_class:"checkbox"|attr:"id:an-group-only" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-searching-since">{{ form.searching_since.label }}</label>
|
|
||||||
{{ form.searching_since|add_class:"input"|attr:"id:an-searching-since"|attr:"type:date" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="notification">
|
|
||||||
<button class="delete"></button>
|
|
||||||
<p>
|
|
||||||
|
|
||||||
{% blocktranslate %}
|
|
||||||
Gibt hier schonmal erste Details zu den Tieren an.
|
|
||||||
Wenn du Details und Fotos zu den Tieren hinzufügen willst oder ihr Geschlecht und Geburtsdatum
|
|
||||||
anpassen
|
|
||||||
willst,
|
|
||||||
kannst du das im nächsten Schritt tun.
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-species">{% translate 'Tierart' %}</label>
|
|
||||||
<div class="select">
|
|
||||||
{{ form.species|attr:"id:an-species" }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-num-animals">{{ form.num_animals.label }}</label>
|
|
||||||
{{ form.num_animals|add_class:"input"|attr:"id:an-num-animals" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-sex">{% translate 'Geschlecht' %}</label>
|
|
||||||
<div class="select">
|
|
||||||
{{ form.sex|attr:"id:an-sex" }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="an-date-of-birth">{{ form.date_of_birth.label }}</label>
|
|
||||||
{{ form.date_of_birth|add_class:"input"|attr:"id:an-date-of-birth"|attr:"type:date" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<input class="button is-primary" type="submit" value="{% translate "Speichern" %}">
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
@@ -1,15 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-header-title">
|
|
||||||
{% blocktrans %}
|
|
||||||
Als {{ user }} kommentieren
|
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
{% crispy comment_form %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -1,5 +1,5 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<form class="btn2 select" action="{% url 'change-language' %}" method="post" onchange='this.form.submit()'>
|
<form class="btn2" action="{% url 'change-language' %}" method="post" onchange='this.form.submit()'>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<select name="language" onchange='this.form.submit()'>
|
<select name="language" onchange='this.form.submit()'>
|
||||||
{% get_current_language as LANGUAGE_CODE_CURRENT %}
|
{% get_current_language as LANGUAGE_CODE_CURRENT %}
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
<!--- See https://docs.djangoproject.com/en/5.2/topics/forms/#reusable-form-templates -->
|
|
||||||
|
|
||||||
{% load custom_tags %}
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="field">
|
|
||||||
|
|
||||||
<label class="label">
|
|
||||||
{{ field.label }}
|
|
||||||
</label>
|
|
||||||
<div class="control">
|
|
||||||
{% if field|widget_type == 'TextInput' %}
|
|
||||||
{{ field|add_class:"input" }}
|
|
||||||
{% elif field|widget_type == 'Select' %}
|
|
||||||
<div class="select">
|
|
||||||
{{ field }}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
{{ field|add_class:"input" }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="help is-danger">
|
|
||||||
{{ field.errors }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
@@ -3,9 +3,7 @@
|
|||||||
|
|
||||||
<section class="header">
|
<section class="header">
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url "index" %}" class="logo">
|
<a href="{% url "index" %}" class="logo"><img src={% static 'fellchensammlung/img/logo_transparent.png' %}></a>
|
||||||
<img src="{% static 'fellchensammlung/img/logo_transparent.png' %}" alt="{% trans 'Notfellchen Logo' %}">
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="profile-card">
|
<div class="profile-card">
|
||||||
@@ -29,7 +27,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="btn2" href="{% url "django_registration_register" %}">{% translate "Registrieren" %}</a>
|
<a class="btn2" href="{% url "django_registration_register" %}">{% translate "Registrieren" %}</a>
|
||||||
<a class="btn2" href="{% url "login" %}"><i class="fa fa-sign-in" aria-label="Login"></i></a>
|
<a class="btn2" href="{% url "login" %}"><i class="fa fa-sign-in" aria-hidden="true"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input id="menu-toggle" type="checkbox"/>
|
<input id="menu-toggle" type="checkbox"/>
|
||||||
<label class='menu-button-container' for="menu-toggle">
|
<label class='menu-button-container' for="menu-toggle">
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% if adoption_notices %}
|
|
||||||
<div class="grid">
|
|
||||||
{% for adoption_notice in adoption_notices %}
|
|
||||||
<div class="cell">
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-adoption-notice-minimal.html" %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p>{% translate "Keine Vermittlungen gefunden." %}</p>
|
|
||||||
{% endif %}
|
|
@@ -1,5 +0,0 @@
|
|||||||
<div class="container-cards">
|
|
||||||
{% for rule in rules %}
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-rule.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
@@ -1,6 +1,6 @@
|
|||||||
{% extends "fellchensammlung/base_generic.html" %}
|
{% extends "fellchensammlung/base_generic.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}<title>{% translate "Karte" %}</title>{% endblock %}
|
{% block title %}<title>{% translate "Karte" %}</title> %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
{% load custom_tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
|
|
||||||
<h2 class="heading-card-adoption-notice title is-4">
|
|
||||||
<a href="{{ adoption_notice.get_absolute_url_bulma }}"> {{ adoption_notice.name }}</a>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="grid mb-0">
|
|
||||||
<div class="cell">
|
|
||||||
<!--- General Information --->
|
|
||||||
<div class="grid">
|
|
||||||
|
|
||||||
<div class="cell">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<b><i class="fa-solid fa-location-dot"></i></b>
|
|
||||||
{% if adoption_notice.location %}
|
|
||||||
{{ adoption_notice.location }}
|
|
||||||
{% else %}
|
|
||||||
{{ adoption_notice.location_string }}
|
|
||||||
{% endif %}</p>
|
|
||||||
</div>
|
|
||||||
<div class="cell">
|
|
||||||
{% include "fellchensammlung/partials/bulma-sex-overview.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if adoption_notice.get_photo %}
|
|
||||||
<div class="adoption-notice-img img-small">
|
|
||||||
<img src="{{ MEDIA_URL }}/{{ adoption_notice.get_photo.image }}"
|
|
||||||
alt="{{ adoption_notice.get_photo.alt_text }}">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,42 +0,0 @@
|
|||||||
{% load custom_tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="header-card-adoption-notice">
|
|
||||||
<h2 class="heading-card-adoption-notice title is-4">
|
|
||||||
<a href="{{ adoption_notice.get_absolute_url_bulma }}"> {{ adoption_notice.name }}</a>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<div class="cell">
|
|
||||||
<!--- General Information --->
|
|
||||||
<div class="grid">
|
|
||||||
|
|
||||||
<div class="cell">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<b><i class="fa-solid fa-location-dot"></i></b>
|
|
||||||
{% if adoption_notice.location %}
|
|
||||||
{{ adoption_notice.location }}
|
|
||||||
{% else %}
|
|
||||||
{{ adoption_notice.location_string }}
|
|
||||||
{% endif %}</p>
|
|
||||||
</div>
|
|
||||||
<div class="cell">
|
|
||||||
{% include "fellchensammlung/partials/bulma-sex-overview.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if adoption_notice.get_photo %}
|
|
||||||
<div class="adoption-notice-img img-small">
|
|
||||||
<img src="{{ MEDIA_URL }}/{{ adoption_notice.get_photo.image }}"
|
|
||||||
alt="{{ adoption_notice.get_photo.alt_text }}">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,38 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h1 class="card-header-title">
|
|
||||||
<a href="{% url 'animal-detail' animal_id=animal.pk %}">{{ animal.name }}</a>
|
|
||||||
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="tags">
|
|
||||||
<div class="tag species">{{ animal.species }}</div>
|
|
||||||
<div class="tag sex">{{ animal.get_sex_display }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
{% if animal.description %}
|
|
||||||
<p>{{ animal.description | render_markdown }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="cell" id="my-gallery">
|
|
||||||
{% for photo in animal.get_photos %}
|
|
||||||
<a href="{{ MEDIA_URL }}/{{ photo.image }}"
|
|
||||||
data-pswp-width="{{ photo.image.width }}"
|
|
||||||
data-pswp-height="{{ photo.image.height }}"
|
|
||||||
target="_blank">
|
|
||||||
<img src="{{ MEDIA_URL }}/{{ photo.image }}" alt="{{ photo.alt_text }}">
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<!--- Assume a user does not have edit permissions on animal if they have no other edit permission --->
|
|
||||||
{% if has_edit_permission %}
|
|
||||||
<div class="card-footer">
|
|
||||||
<a class="card-footer-item button" href="{% url 'animal-edit' animal_id=animal.pk %}">{% translate 'Bearbeiten' %}</a>
|
|
||||||
<a class="card-footer-item button"
|
|
||||||
href="{% url 'animal-add-photo' animal_id=animal.pk %}">{% translate 'Foto hinzufügen' %}</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -1,25 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2 class="card-header-title title is-2">{% translate 'Kommentare' %}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
{% if adoption_notice.comments %}
|
|
||||||
{% for comment in adoption_notice.comments %}
|
|
||||||
{% include "fellchensammlung/partials/bulma-partial-comment.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p class="is-italic">{% translate 'Noch keine Kommentare' %}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<footer class="card-footer">
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% include "fellchensammlung/forms/bulma-form-comment.html" %}
|
|
||||||
{% else %}
|
|
||||||
<p class="card-footer-item">
|
|
||||||
{% translate 'Du musst dich einloggen um Kommentare zu hinterlassen' %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
@@ -1,27 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="card-header-title content">
|
|
||||||
<b class="">{{ comment.user }}</b> <span class="tag"><time class="">{{ comment.created_at }}</time></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="content">
|
|
||||||
{{ comment.text | render_markdown }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
|
|
||||||
<a class="card-footer-item is-danger" href="{{ comment.get_report_url }}">
|
|
||||||
<span class="icon-text">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fa-solid fa-flag"></i>
|
|
||||||
</span>
|
|
||||||
<span>{% trans 'Melden' %}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
@@ -1,149 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<!-- add MapLibre JavaScript and CSS -->
|
|
||||||
<script src="{% settings_value "MAP_TILE_SERVER" %}/assets/lib/maplibre-gl/maplibre-gl.js"></script>
|
|
||||||
<link href="{% settings_value "MAP_TILE_SERVER" %}/assets/lib/maplibre-gl/maplibre-gl.css" rel="stylesheet"/>
|
|
||||||
|
|
||||||
<!-- add Turf see https://maplibre.org/maplibre-gl-js/docs/examples/draw-a-radius/ -->
|
|
||||||
<script src="{% static 'fellchensammlung/js/turf.min.js' %}"></script>
|
|
||||||
|
|
||||||
<!-- add container for the map -->
|
|
||||||
<div id="map" class="map"></div>
|
|
||||||
|
|
||||||
<!-- start map -->
|
|
||||||
<script>
|
|
||||||
{% if zoom_level %}
|
|
||||||
var zoom_level = {{ zoom_level }};
|
|
||||||
{% else %}
|
|
||||||
var zoom_level = 4;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if map_center %}
|
|
||||||
var map_center = [{{ map_center.longitude | pointdecimal }}, {{ map_center.latitude | pointdecimal }}];
|
|
||||||
{% else %}
|
|
||||||
var map_center = [10.49, 50.68]; <!-- Point middle of Germany -->
|
|
||||||
zoom_level = 4; //Overwrite zoom level when no place is found
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
let map = new maplibregl.Map({
|
|
||||||
container: 'map',
|
|
||||||
style: '{% static "fellchensammlung/map/styles/colorful/style.json" %}',
|
|
||||||
center: map_center,
|
|
||||||
zoom: zoom_level
|
|
||||||
});
|
|
||||||
|
|
||||||
map.addControl(new maplibregl.FullscreenControl());
|
|
||||||
map.addControl(new maplibregl.NavigationControl({showCompass: false}));
|
|
||||||
|
|
||||||
{% for adoption_notice in adoption_notices_map %}
|
|
||||||
{% if adoption_notice.location %}
|
|
||||||
// create the popup
|
|
||||||
const popup_{{ forloop.counter }} = new maplibregl.Popup({offset: 25}).setHTML(`{% include "fellchensammlung/partials/bulma-partial-adoption-notice-minimal-map.html" %}`);
|
|
||||||
|
|
||||||
// create DOM element for the marker
|
|
||||||
const el_{{ forloop.counter }} = document.createElement('div');
|
|
||||||
el_{{ forloop.counter }}.id = 'marker_{{ forloop.counter }}';
|
|
||||||
el_{{ forloop.counter }}.classList.add('marker');
|
|
||||||
|
|
||||||
const location_popup_{{ forloop.counter }} = [{{ adoption_notice.location.longitude | pointdecimal }}, {{ adoption_notice.location.latitude | pointdecimal }}];
|
|
||||||
// create the marker
|
|
||||||
new maplibregl.Marker({element: el_{{ forloop.counter }}})
|
|
||||||
.setLngLat(location_popup_{{ forloop.counter }})
|
|
||||||
.setPopup(popup_{{ forloop.counter }}) // sets a popup on this marker
|
|
||||||
.addTo(map);
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% for rescue_organization in rescue_organizations %}
|
|
||||||
{% if rescue_organization.location %}
|
|
||||||
// create the popup
|
|
||||||
const popup_{{ forloop.counter }} = new maplibregl.Popup({offset: 25}).setHTML(`{% include "fellchensammlung/partials/partial-rescue-organization.html" %}`);
|
|
||||||
|
|
||||||
// create DOM element for the marker
|
|
||||||
const el_{{ forloop.counter }} = document.createElement('div');
|
|
||||||
el_{{ forloop.counter }}.id = 'marker_{{ forloop.counter }}';
|
|
||||||
el_{{ forloop.counter }}.classList.add('animal-shelter-marker', 'marker');
|
|
||||||
|
|
||||||
const location_popup_{{ forloop.counter }} = [{{ rescue_organization.location.longitude | pointdecimal }}, {{ rescue_organization.location.latitude | pointdecimal }}];
|
|
||||||
// create the marker
|
|
||||||
new maplibregl.Marker({element: el_{{ forloop.counter }}})
|
|
||||||
.setLngLat(location_popup_{{ forloop.counter }})
|
|
||||||
.setPopup(popup_{{ forloop.counter }}) // sets a popup on this marker
|
|
||||||
.addTo(map);
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
map.on('load', async () => {
|
|
||||||
image = await map.loadImage('{% static "fellchensammlung/img/pin.png" %}');
|
|
||||||
map.addImage('pin', image.data);
|
|
||||||
{% for map_pin in map_pins %}
|
|
||||||
map.addSource('point_{{ forloop.counter }}', {
|
|
||||||
'type': 'geojson',
|
|
||||||
'data': {
|
|
||||||
'type': 'FeatureCollection',
|
|
||||||
'features': [
|
|
||||||
{
|
|
||||||
'type': 'Feature',
|
|
||||||
'geometry': {
|
|
||||||
'type': 'Point',
|
|
||||||
'coordinates': [{{ map_pin.location.longitude | pointdecimal }}, {{ map_pin.location.latitude | pointdecimal }}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
map.addLayer({
|
|
||||||
'id': 'point_{{ forloop.counter }}',
|
|
||||||
'type': 'circle',
|
|
||||||
'source': 'point_{{ forloop.counter }}',
|
|
||||||
'paint': {
|
|
||||||
'circle-radius': 18,
|
|
||||||
'circle-color': '#ff878980'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
{% endfor %}
|
|
||||||
});
|
|
||||||
|
|
||||||
{% if search_center %}
|
|
||||||
var search_center = [{{ search_center.longitude | pointdecimal }}, {{ search_center.latitude | pointdecimal }}];
|
|
||||||
map.on('load', () => {
|
|
||||||
const radius = {{ search_radius }}; // kilometer
|
|
||||||
const options = {
|
|
||||||
steps: 64,
|
|
||||||
units: 'kilometers'
|
|
||||||
};
|
|
||||||
const circle = turf.circle(search_center, radius, options);
|
|
||||||
|
|
||||||
// Add the circle as a GeoJSON source
|
|
||||||
map.addSource('location-radius', {
|
|
||||||
type: 'geojson',
|
|
||||||
data: circle
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add a fill layer with some transparency
|
|
||||||
map.addLayer({
|
|
||||||
id: 'location-radius',
|
|
||||||
type: 'fill',
|
|
||||||
source: 'location-radius',
|
|
||||||
paint: {
|
|
||||||
'fill-color': 'rgba(140,207,255,0.3)',
|
|
||||||
'fill-opacity': 0.5
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add a line layer to draw the circle outline
|
|
||||||
map.addLayer({
|
|
||||||
id: 'location-radius-outline',
|
|
||||||
type: 'line',
|
|
||||||
source: 'location-radius',
|
|
||||||
paint: {
|
|
||||||
'line-color': '#0094ff',
|
|
||||||
'line-width': 3
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</script>
|
|
@@ -1,9 +0,0 @@
|
|||||||
{% load custom_tags %}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2 class="card-header-title">{{ rule.title }}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="content">{{ rule.rule_text | render_markdown }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -1,44 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
<div class="grid">
|
|
||||||
{% if adoption_notice.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 }} x </span>
|
|
||||||
<span class="icon">
|
|
||||||
<img class="icon" src="{% static 'fellchensammlung/img/sexes/Female.png' %}"
|
|
||||||
alt="{% translate 'weibliche Tiere' %}">
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if adoption_notice.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="icon">
|
|
||||||
<img class="icon"
|
|
||||||
src="{% static 'fellchensammlung/img/sexes/Intersex.png' %}"
|
|
||||||
alt="{% translate 'intersexuelle Tiere' %}">
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if adoption_notice.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="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 %}
|
|
||||||
<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="icon">
|
|
||||||
<img class="icon"
|
|
||||||
src="{% static 'fellchensammlung/img/sexes/Male Neutered.png' %}"
|
|
||||||
alt="{% translate 'männlich, kastrierte Tiere' %}">
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
@@ -1,21 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
{% load custom_tags %}
|
|
||||||
<div class="card">
|
|
||||||
<h1>
|
|
||||||
<a href="{{ rescue_org.get_absolute_url }}">{{ rescue_org.name }}</a>
|
|
||||||
</h1>
|
|
||||||
<i>{% translate 'Zuletzt geprüft:' %} {{ rescue_org.last_checked_hr }}</i>
|
|
||||||
{% if rescue_org.website %}
|
|
||||||
<p>{% translate "Website" %}: <a href="{{ rescue_org.website }}">{{ rescue_org.website }}</a></p>
|
|
||||||
{% endif %}
|
|
||||||
<div class="container-edit-buttons">
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden"
|
|
||||||
name="rescue_organization_id"
|
|
||||||
value="{{ rescue_org.pk }}">
|
|
||||||
<input type="hidden" name="action" value="checked">
|
|
||||||
<button class="btn" type="submit">{% translate "Organisation geprüft" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -3,8 +3,8 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<!-- add MapLibre JavaScript and CSS -->
|
<!-- add MapLibre JavaScript and CSS -->
|
||||||
<script src="{% settings_value "MAP_TILE_SERVER" %}/assets/lib/maplibre-gl/maplibre-gl.js"></script>
|
<script src="{% settings_value "MAP_TILE_SERVER" %}/assets/maplibre-gl/maplibre-gl.js"></script>
|
||||||
<link href="{% settings_value "MAP_TILE_SERVER" %}/assets/lib/maplibre-gl/maplibre-gl.css" rel="stylesheet"/>
|
<link href="{% settings_value "MAP_TILE_SERVER" %}/assets/maplibre-gl/maplibre-gl.css" rel="stylesheet"/>
|
||||||
|
|
||||||
<!-- add Turf see https://maplibre.org/maplibre-gl-js/docs/examples/draw-a-radius/ -->
|
<!-- add Turf see https://maplibre.org/maplibre-gl-js/docs/examples/draw-a-radius/ -->
|
||||||
<script src="{% static 'fellchensammlung/js/turf.min.js' %}"></script>
|
<script src="{% static 'fellchensammlung/js/turf.min.js' %}"></script>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
let map = new maplibregl.Map({
|
let map = new maplibregl.Map({
|
||||||
container: 'map',
|
container: 'map',
|
||||||
style: '{% static "fellchensammlung/map/styles/colorful/style.json" %}',
|
style: '{% static "fellchensammlung/map/styles/colorful.json" %}',
|
||||||
center: map_center,
|
center: map_center,
|
||||||
zoom: zoom_level
|
zoom: zoom_level
|
||||||
}).addControl(new maplibregl.NavigationControl());
|
}).addControl(new maplibregl.NavigationControl());
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
image = await map.loadImage('{% static "fellchensammlung/img/pin.png" %}');
|
image = await map.loadImage('{% static "fellchensammlung/img/pin.png" %}');
|
||||||
map.addImage('pin', image.data);
|
map.addImage('pin', image.data);
|
||||||
{% for map_pin in map_pins %}
|
{% for map_pin in map_pins %}
|
||||||
map.addSource('point_{{ forloop.counter }}', {
|
map.addSource('point', {
|
||||||
'type': 'geojson',
|
'type': 'geojson',
|
||||||
'data': {
|
'data': {
|
||||||
'type': 'FeatureCollection',
|
'type': 'FeatureCollection',
|
||||||
@@ -91,16 +91,16 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
map.addLayer({
|
|
||||||
'id': 'point_{{ forloop.counter }}',
|
|
||||||
'type': 'circle',
|
|
||||||
'source': 'point_{{ forloop.counter }}',
|
|
||||||
'paint': {
|
|
||||||
'circle-radius': 18,
|
|
||||||
'circle-color': '#ff878980'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
map.addLayer({
|
||||||
|
'id': 'pints',
|
||||||
|
'type': 'symbol',
|
||||||
|
'source': 'point',
|
||||||
|
'layout': {
|
||||||
|
'icon-image': 'pin',
|
||||||
|
'icon-size': 0.1
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if search_center %}
|
{% if search_center %}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="report card">
|
<div class="report card">
|
||||||
<h2>
|
<h2>
|
||||||
{% translate 'Meldung von ' %} <a href="{{ report.reported_content_url }}"><i>{{ report.reported_content }}</i></a>
|
{% blocktranslate %}
|
||||||
|
Meldung von {{ report.reported_content }}
|
||||||
|
{% endblocktranslate %}
|
||||||
</h2>
|
</h2>
|
||||||
{% if report.reported_broken_rules %}
|
{% if report.reported_broken_rules %}
|
||||||
{% translate "Regeln gegen die Verstoßen wurde" %}
|
{% translate "Regeln gegen die Verstoßen wurde" %}
|
||||||
@@ -11,25 +13,19 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>
|
<p><b>{% translate "Kommentar zur Meldung" %}:</b>
|
||||||
{% if report.user_comment %}
|
{{ report.user_comment }}
|
||||||
<b>{% translate "Kommentar zur Meldung" %}:</b> {{ report.user_comment }}
|
|
||||||
{% else %}
|
|
||||||
<i>{% translate 'Es wurde kein Kommentar zur Meldung hinzugefügt.' %}</i>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
{% if is_mod_or_above %}
|
<div class="container-edit-buttons">
|
||||||
<div class="container-edit-buttons">
|
<form action="allow" class="">
|
||||||
<form action="allow" class="">
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
<input type="hidden" name="report_id" value="{{ report.pk }}">
|
||||||
<input type="hidden" name="report_id" value="{{ report.pk }}">
|
<button class="btn allow" type="submit">{% translate "Inhalt genehmigen" %}</button>
|
||||||
<button class="btn allow" type="submit">{% translate "Inhalt genehmigen" %}</button>
|
</form>
|
||||||
</form>
|
<form action="disallow" class="">
|
||||||
<form action="disallow" class="">
|
{% csrf_token %}
|
||||||
{% csrf_token %}
|
<input type="hidden" name="report_id" value="{{ report.pk }}">
|
||||||
<input type="hidden" name="report_id" value="{{ report.pk }}">
|
<button class="btn allow" type="submit">{% translate "Inhalt als gesperrt kennzeichnen" %}</button>
|
||||||
<button class="btn allow" type="submit">{% translate "Inhalt als gesperrt kennzeichnen" %}</button>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
@@ -1,12 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_generic.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% translate "Aktualitätscheck" %}</h1>
|
|
||||||
<p>{% translate "Überprüfe ob im Tierheim neue Vermittlungen ein Zuhause suchen" %}</p>
|
|
||||||
<div class="container-cards spaced">
|
|
||||||
<h1>{% translate 'Organisation zur Überprüfung' %}</h1>
|
|
||||||
{% for rescue_org in rescue_orgs %}
|
|
||||||
{% include "fellchensammlung/partials/partial-check-rescue-org.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@@ -11,7 +11,7 @@
|
|||||||
<input type="hidden" name="longitude" maxlength="200" id="longitude">
|
<input type="hidden" name="longitude" maxlength="200" id="longitude">
|
||||||
<input type="hidden" name="latitude" maxlength="200" id="latitude">
|
<input type="hidden" name="latitude" maxlength="200" id="latitude">
|
||||||
<input type="hidden" id="place_id" name="place_id">
|
<input type="hidden" id="place_id" name="place_id">
|
||||||
{{ search_form }}
|
{{ search_form.as_p }}
|
||||||
<ul id="results"></ul>
|
<ul id="results"></ul>
|
||||||
<div class="container-edit-buttons">
|
<div class="container-edit-buttons">
|
||||||
<button class="btn" type="submit" value="search" name="search">
|
<button class="btn" type="submit" value="search" name="search">
|
||||||
|
@@ -1,35 +0,0 @@
|
|||||||
{% extends "fellchensammlung/base_generic.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
{% block title %}<title>{% translate "Styleguide" %}</title>{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>This is a heading</h1>
|
|
||||||
<p>And this is a short paragraph below</p>
|
|
||||||
<div class="container-cards">
|
|
||||||
<h2>Card Containers</h2>
|
|
||||||
<div class="card">
|
|
||||||
<h3>I am a card</h3>
|
|
||||||
<p>Cards are responsive. Use them to display multiple items of the same category</p>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>Photos</h3>
|
|
||||||
<p>Cards are responsive. Use them to display multiple items of the same category</p>
|
|
||||||
<img src="{% static 'fellchensammlung/img/example_rat_single.png' %}" alt="A rat sitting on a wooden house">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container-cards">
|
|
||||||
<form class="form-search card half" method="post">
|
|
||||||
<label for="inputA">Input Alpha</label>
|
|
||||||
<input name="inputA" maxlength="200" id="inputA">
|
|
||||||
<label for="inputB">Beta</label>
|
|
||||||
<input name="inputB" maxlength="200" id="inputB">
|
|
||||||
<label for="id_location_string">Ort</label>
|
|
||||||
<input name="location_string" id="id_location_string">
|
|
||||||
</form>
|
|
||||||
<div class="card half">
|
|
||||||
{% include "fellchensammlung/partials/partial-map.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
|
||||||
{% endblock %}
|
|
@@ -49,7 +49,6 @@ def get_oxitraffic_script_if_enabled():
|
|||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def pointdecimal(value):
|
def pointdecimal(value):
|
||||||
@@ -58,7 +57,6 @@ def pointdecimal(value):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def domain(url):
|
def domain(url):
|
||||||
@@ -70,17 +68,6 @@ def domain(url):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def settings_value(name):
|
def settings_value(name):
|
||||||
return getattr(settings, name)
|
return getattr(settings, name)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='add_class')
|
|
||||||
def add_class(field, css_class):
|
|
||||||
return field.as_widget(attrs={"class": css_class})
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def widget_type(field):
|
|
||||||
return field.field.widget.__class__.__name__
|
|
||||||
|
@@ -74,21 +74,13 @@ class GeoFeature:
|
|||||||
geofeatures = []
|
geofeatures = []
|
||||||
for feature in result["features"]:
|
for feature in result["features"]:
|
||||||
geojson = {}
|
geojson = {}
|
||||||
# Necessary features
|
|
||||||
geojson['place_id'] = feature["properties"]["osm_id"]
|
|
||||||
geojson['lat'] = feature["geometry"]["coordinates"][1]
|
|
||||||
geojson['lon'] = feature["geometry"]["coordinates"][0]
|
|
||||||
try:
|
try:
|
||||||
geojson['name'] = feature["properties"]["name"]
|
geojson['name'] = feature["properties"]["name"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
geojson['name'] = feature["properties"]["osm_id"]
|
geojson['name'] = feature["properties"]["street"]
|
||||||
|
geojson['place_id'] = feature["properties"]["osm_id"]
|
||||||
optional_keys = ["housenumber", "street", "city", "postcode", "county", "countrycode"]
|
geojson['lat'] = feature["geometry"]["coordinates"][1]
|
||||||
for key in optional_keys:
|
geojson['lon'] = feature["geometry"]["coordinates"][0]
|
||||||
try:
|
|
||||||
geojson[key] = feature["properties"][key]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
geofeatures.append(geojson)
|
geofeatures.append(geojson)
|
||||||
return geofeatures
|
return geofeatures
|
||||||
|
|
||||||
@@ -169,7 +161,6 @@ class LocationProxy:
|
|||||||
"""
|
"""
|
||||||
self.geo_api = GeoAPI()
|
self.geo_api = GeoAPI()
|
||||||
geofeatures = self.geo_api.get_geojson_for_query(location_string)
|
geofeatures = self.geo_api.get_geojson_for_query(location_string)
|
||||||
|
|
||||||
if geofeatures is None:
|
if geofeatures is None:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
result = geofeatures[0]
|
result = geofeatures[0]
|
||||||
@@ -177,12 +168,6 @@ class LocationProxy:
|
|||||||
self.place_id = result["place_id"]
|
self.place_id = result["place_id"]
|
||||||
self.latitude = result["lat"]
|
self.latitude = result["lat"]
|
||||||
self.longitude = result["lon"]
|
self.longitude = result["lon"]
|
||||||
optional_keys = ["housenumber", "street", "city", "postcode", "county", "countrycode"]
|
|
||||||
for key in optional_keys:
|
|
||||||
try:
|
|
||||||
self.__setattr__(key, result[key])
|
|
||||||
except KeyError:
|
|
||||||
self.__setattr__(key, None)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.place_id == other.place_id
|
return self.place_id == other.place_id
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
from django.utils import translation
|
|
||||||
|
|
||||||
from fellchensammlung.models import Language, Text
|
|
||||||
|
|
||||||
|
|
||||||
def get_text_by_language(text_code, lang=None):
|
|
||||||
if lang is None:
|
|
||||||
language_code = translation.get_language()
|
|
||||||
lang = Language.objects.get(languagecode=language_code)
|
|
||||||
return Text.objects.get(text_code=text_code, language=lang, )
|
|
||||||
|
|
||||||
|
|
||||||
def get_texts_by_language(text_codes):
|
|
||||||
language_code = translation.get_language()
|
|
||||||
lang = Language.objects.get(languagecode=language_code)
|
|
||||||
|
|
||||||
texts = {}
|
|
||||||
for text_code in text_codes:
|
|
||||||
try:
|
|
||||||
texts[text_code] = get_text_by_language(text_code, lang)
|
|
||||||
except Text.DoesNotExist:
|
|
||||||
texts[text_code] = None
|
|
||||||
return texts
|
|
@@ -6,7 +6,7 @@ from ..forms import AdoptionNoticeSearchForm
|
|||||||
from ..models import SearchSubscription, AdoptionNotice, AdoptionNoticeNotification, SexChoicesWithAll, Location
|
from ..models import SearchSubscription, AdoptionNotice, AdoptionNoticeNotification, SexChoicesWithAll, Location
|
||||||
|
|
||||||
|
|
||||||
def notify_search_subscribers(adoption_notice: AdoptionNotice, only_if_active: bool = True):
|
def notify_search_subscribers(adoption_notice: AdoptionNotice, only_if_active : bool = True):
|
||||||
"""
|
"""
|
||||||
This functions checks for all search subscriptions if the new adoption notice fits the search.
|
This functions checks for all search subscriptions if the new adoption notice fits the search.
|
||||||
If the new adoption notice fits the search subscription, it sends a notification to the user that created the search.
|
If the new adoption notice fits the search subscription, it sends a notification to the user that created the search.
|
||||||
@@ -36,7 +36,7 @@ class Search:
|
|||||||
self.sex = None
|
self.sex = None
|
||||||
self.area_search = None
|
self.area_search = None
|
||||||
self.max_distance = None
|
self.max_distance = None
|
||||||
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
self.location = None # Can either be Location (DjangoModel) or LocationProxy
|
||||||
self.place_not_found = False # Indicates that a location was given but could not be geocoded
|
self.place_not_found = False # Indicates that a location was given but could not be geocoded
|
||||||
self.search_form = None
|
self.search_form = None
|
||||||
# Either place_id or location string must be set for area search
|
# Either place_id or location string must be set for area search
|
||||||
@@ -47,6 +47,7 @@ class Search:
|
|||||||
elif search_subscription:
|
elif search_subscription:
|
||||||
self.search_from_search_subscription(search_subscription)
|
self.search_from_search_subscription(search_subscription)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Search: {self.sex=}, {self.location=}, {self.area_search=}, {self.max_distance=}"
|
return f"Search: {self.sex=}, {self.location=}, {self.area_search=}, {self.max_distance=}"
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ class Search:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_adoption_notices(self):
|
def get_adoption_notices(self):
|
||||||
adoptions = AdoptionNotice.objects.order_by("-created_at")
|
adoptions = AdoptionNotice.objects.order_by("-created_at")
|
||||||
# Filter for active adoption notices
|
# Filter for active adoption notices
|
||||||
@@ -116,21 +118,13 @@ class Search:
|
|||||||
else:
|
else:
|
||||||
self.search_form = AdoptionNoticeSearchForm()
|
self.search_form = AdoptionNoticeSearchForm()
|
||||||
|
|
||||||
def search_from_predefined_i_location(self, i_location, max_distance=100):
|
|
||||||
self.sex = SexChoicesWithAll.ALL
|
|
||||||
self.location = i_location.location
|
|
||||||
self.area_search = True
|
|
||||||
self.search_form = AdoptionNoticeSearchForm(initial={"location_string": self.location.name,
|
|
||||||
"max_distance": max_distance,
|
|
||||||
"sex": SexChoicesWithAll.ALL})
|
|
||||||
self.max_distance = max_distance
|
|
||||||
|
|
||||||
def search_from_search_subscription(self, search_subscription: SearchSubscription):
|
def search_from_search_subscription(self, search_subscription: SearchSubscription):
|
||||||
self.sex = search_subscription.sex
|
self.sex = search_subscription.sex
|
||||||
self.location = search_subscription.location
|
self.location = search_subscription.location
|
||||||
self.area_search = True
|
self.area_search = True
|
||||||
self.max_distance = search_subscription.max_distance
|
self.max_distance = search_subscription.max_distance
|
||||||
|
|
||||||
|
|
||||||
def subscribe(self, user):
|
def subscribe(self, user):
|
||||||
logging.info(f"{user} subscribed to search")
|
logging.info(f"{user} subscribed to search")
|
||||||
if isinstance(self.location, LocationProxy):
|
if isinstance(self.location, LocationProxy):
|
||||||
|
@@ -7,18 +7,8 @@ from .feeds import LatestAdoptionNoticesFeed
|
|||||||
from . import views
|
from . import views
|
||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
from django.contrib.sitemaps.views import sitemap
|
|
||||||
from .sitemap import StaticViewSitemap, AdoptionNoticeSitemap, AnimalSitemap
|
|
||||||
|
|
||||||
sitemaps = {
|
|
||||||
"static": StaticViewSitemap,
|
|
||||||
"vermittlungen": AdoptionNoticeSitemap,
|
|
||||||
"tiere": AnimalSitemap,
|
|
||||||
}
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.index, name="index"),
|
||||||
path("bulma/", views.index_bulma, name="index-bulma"),
|
|
||||||
path("rss/", LatestAdoptionNoticesFeed(), name="rss"),
|
path("rss/", LatestAdoptionNoticesFeed(), name="rss"),
|
||||||
path("metrics/", views.metrics, name="metrics"),
|
path("metrics/", views.metrics, name="metrics"),
|
||||||
# ex: /animal/5/
|
# ex: /animal/5/
|
||||||
@@ -29,15 +19,12 @@ urlpatterns = [
|
|||||||
path("tier/<int:animal_id>/add-photo", views.add_photo_to_animal, name="animal-add-photo"),
|
path("tier/<int:animal_id>/add-photo", views.add_photo_to_animal, name="animal-add-photo"),
|
||||||
# ex: /adoption_notice/7/
|
# ex: /adoption_notice/7/
|
||||||
path("vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail, name="adoption-notice-detail"),
|
path("vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail, name="adoption-notice-detail"),
|
||||||
path("bulma/vermittlung/<int:adoption_notice_id>/", views.adoption_notice_detail_bulma, name="adoption-notice-detail-bulma"),
|
|
||||||
# ex: /adoption_notice/7/edit
|
# ex: /adoption_notice/7/edit
|
||||||
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
path("vermittlung/<int:adoption_notice_id>/edit", views.adoption_notice_edit, name="adoption-notice-edit"),
|
||||||
# ex: /vermittlung/5/add-photo
|
# ex: /vermittlung/5/add-photo
|
||||||
path("vermittlung/<int:adoption_notice_id>/add-photo", views.add_photo_to_adoption_notice,
|
path("vermittlung/<int:adoption_notice_id>/add-photo", views.add_photo_to_adoption_notice, name="adoption-notice-add-photo"),
|
||||||
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,
|
path("vermittlung/<int:adoption_notice_id>/add-animal", views.adoption_notice_add_animal, name="adoption-notice-add-animal"),
|
||||||
name="adoption-notice-add-animal"),
|
|
||||||
|
|
||||||
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
path("tierschutzorganisationen/", views.list_rescue_organizations, name="rescue-organizations"),
|
||||||
path("organisation/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
path("organisation/<int:rescue_organization_id>/", views.detail_view_rescue_organization,
|
||||||
@@ -45,21 +32,12 @@ urlpatterns = [
|
|||||||
|
|
||||||
# ex: /search/
|
# ex: /search/
|
||||||
path("suchen/", views.search, name="search"),
|
path("suchen/", views.search, name="search"),
|
||||||
path("bulma/suchen/", views.search_bulma, name="search-bulma"),
|
|
||||||
path("suchen/<slug:important_location_slug>", views.search_important_locations, name="search-by-location"),
|
|
||||||
# ex: /map/
|
# ex: /map/
|
||||||
path("map/", views.map, name="map"),
|
path("map/", views.map, name="map"),
|
||||||
# ex: /map/
|
|
||||||
path("bulma/map/", views.map_bulma, name="map-bulma"),
|
|
||||||
# ex: /vermitteln/
|
# ex: /vermitteln/
|
||||||
path("vermitteln/", views.add_adoption_notice, name="add-adoption"),
|
path("vermitteln/", views.add_adoption_notice, name="add-adoption"),
|
||||||
path("bulma/vermitteln/", views.add_adoption_notice_bulma, name="add-adoption-bulma"),
|
|
||||||
|
|
||||||
path("ueber-uns/", views.about, name="about"),
|
path("ueber-uns/", views.about, name="about"),
|
||||||
path("bulma/ueber-uns/", views.about_bulma, name="about-bulma"),
|
|
||||||
path("impressum/", views.imprint, name="imprint"),
|
|
||||||
path("terms-of-service/", views.terms_of_service, name="terms-of-service"),
|
|
||||||
path("datenschutz/", views.privacy, name="privacy"),
|
|
||||||
|
|
||||||
################
|
################
|
||||||
## Moderation ##
|
## Moderation ##
|
||||||
@@ -70,11 +48,9 @@ urlpatterns = [
|
|||||||
path("meldung/<uuid:report_id>/", views.report_detail, name="report-detail"),
|
path("meldung/<uuid:report_id>/", views.report_detail, name="report-detail"),
|
||||||
path("meldung/<uuid:report_id>/sucess", views.report_detail_success, name="report-detail-success"),
|
path("meldung/<uuid:report_id>/sucess", views.report_detail_success, name="report-detail-success"),
|
||||||
path("modqueue/", views.modqueue, name="modqueue"),
|
path("modqueue/", views.modqueue, name="modqueue"),
|
||||||
|
|
||||||
path("updatequeue/", views.updatequeue, name="updatequeue"),
|
path("updatequeue/", views.updatequeue, name="updatequeue"),
|
||||||
|
|
||||||
path("organization-check/", views.rescue_organization_check, name="organization-check"),
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
## USERS ##
|
## USERS ##
|
||||||
###########
|
###########
|
||||||
@@ -119,11 +95,4 @@ urlpatterns = [
|
|||||||
###################
|
###################
|
||||||
path('external-site/', views.external_site_warning, name="external-site"),
|
path('external-site/', views.external_site_warning, name="external-site"),
|
||||||
|
|
||||||
###############
|
|
||||||
## TECHNICAL ##
|
|
||||||
###############
|
|
||||||
path("sitemap.xml", sitemap, {"sitemaps": sitemaps}, name="django.contrib.sitemaps.views.sitemap"),
|
|
||||||
path("styleguide", views.styleguide, name="styleguide"),
|
|
||||||
path("styleguide-bulma", views.styleguide_bulma, name="styleguide-bulma"),
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@@ -2,8 +2,7 @@ import logging
|
|||||||
|
|
||||||
from django.contrib.auth.views import redirect_to_login
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
|
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
|
||||||
from django.http.response import HttpResponseForbidden
|
from django.shortcuts import render, redirect
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
|
||||||
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
|
||||||
@@ -18,14 +17,11 @@ 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, TrustLevel, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification, \
|
Species, Log, Timestamp, TrustLevel, SexChoicesWithAll, SearchSubscription, AdoptionNoticeNotification
|
||||||
ImportantLocation
|
|
||||||
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
|
||||||
BulmaAdoptionNoticeForm
|
|
||||||
from .models import Language, Announcement
|
from .models import Language, Announcement
|
||||||
from .tools import i18n
|
|
||||||
from .tools.geo import GeoAPI, zoom_level_for_radius
|
from .tools.geo import GeoAPI, zoom_level_for_radius
|
||||||
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, \
|
||||||
@@ -66,22 +62,6 @@ def index(request):
|
|||||||
return render(request, 'fellchensammlung/index.html', context=context)
|
return render(request, 'fellchensammlung/index.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def index_bulma(request):
|
|
||||||
"""View function for home page of site."""
|
|
||||||
latest_adoption_list = AdoptionNotice.objects.filter(
|
|
||||||
adoptionnoticestatus__major_status=AdoptionNoticeStatus.ACTIVE).order_by("-created_at")
|
|
||||||
active_adoptions = [adoption for adoption in latest_adoption_list if adoption.is_active]
|
|
||||||
language_code = translation.get_language()
|
|
||||||
lang = Language.objects.get(languagecode=language_code)
|
|
||||||
active_announcements = Announcement.get_active_announcements(lang)
|
|
||||||
|
|
||||||
context = {"adoption_notices": active_adoptions[:5], "adoption_notices_map": active_adoptions,
|
|
||||||
"announcements": active_announcements}
|
|
||||||
Text.get_texts(["how_to", "introduction"], lang, context)
|
|
||||||
|
|
||||||
return render(request, 'fellchensammlung/bulma-index.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
def change_language(request):
|
def change_language(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
language_code = request.POST.get('language')
|
language_code = request.POST.get('language')
|
||||||
@@ -99,11 +79,9 @@ def change_language(request):
|
|||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
return render(request, 'fellchensammlung/index.html')
|
return render(request, 'fellchensammlung/index.html')
|
||||||
else:
|
|
||||||
return render(request, 'fellchensammlung/index.html')
|
|
||||||
|
|
||||||
|
|
||||||
def adoption_notice_detail(request, adoption_notice_id, template=None):
|
def adoption_notice_detail(request, adoption_notice_id):
|
||||||
adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id)
|
adoption_notice = AdoptionNotice.objects.get(id=adoption_notice_id)
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
try:
|
try:
|
||||||
@@ -155,20 +133,12 @@ def adoption_notice_detail(request, adoption_notice_id, template=None):
|
|||||||
elif action == "subscribe":
|
elif action == "subscribe":
|
||||||
return redirect_to_login(next=request.path)
|
return redirect_to_login(next=request.path)
|
||||||
else:
|
else:
|
||||||
return HttpResponseForbidden()
|
raise PermissionDenied
|
||||||
else:
|
else:
|
||||||
comment_form = CommentForm(instance=adoption_notice)
|
comment_form = CommentForm(instance=adoption_notice)
|
||||||
context = {"adoption_notice": adoption_notice, "comment_form": comment_form, "user": request.user,
|
context = {"adoption_notice": adoption_notice, "comment_form": comment_form, "user": request.user,
|
||||||
"has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed}
|
"has_edit_permission": has_edit_permission, "is_subscribed": is_subscribed}
|
||||||
if template is not None:
|
return render(request, 'fellchensammlung/details/detail_adoption_notice.html', context=context)
|
||||||
return render(request, template, context=context)
|
|
||||||
else:
|
|
||||||
return render(request, 'fellchensammlung/details/detail_adoption_notice.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
def adoption_notice_detail_bulma(request, adoption_notice_id):
|
|
||||||
return adoption_notice_detail(request, adoption_notice_id,
|
|
||||||
template='fellchensammlung/details/bulma-detail-adoption-notice.html')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
@@ -203,31 +173,7 @@ def animal_detail(request, animal_id):
|
|||||||
return render(request, 'fellchensammlung/details/detail_animal.html', context=context)
|
return render(request, 'fellchensammlung/details/detail_animal.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def search_important_locations(request, important_location_slug):
|
def search(request):
|
||||||
i_location = get_object_or_404(ImportantLocation, slug=important_location_slug)
|
|
||||||
search = Search()
|
|
||||||
search.search_from_predefined_i_location(i_location)
|
|
||||||
context = {"adoption_notices": search.get_adoption_notices(),
|
|
||||||
"search_form": search.search_form,
|
|
||||||
"place_not_found": search.place_not_found,
|
|
||||||
"subscribed_search": None,
|
|
||||||
"searched": False,
|
|
||||||
"adoption_notices_map": AdoptionNotice.get_active_ANs(),
|
|
||||||
"map_center": search.position,
|
|
||||||
"search_center": search.position,
|
|
||||||
"map_pins": [search],
|
|
||||||
"location": search.location,
|
|
||||||
"search_radius": search.max_distance,
|
|
||||||
"zoom_level": zoom_level_for_radius(search.max_distance),
|
|
||||||
"geocoding_api_url": settings.GEOCODING_API_URL, }
|
|
||||||
return render(request, 'fellchensammlung/search.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
def search_bulma(request):
|
|
||||||
return search(request, "fellchensammlung/bulma-search.html")
|
|
||||||
|
|
||||||
|
|
||||||
def search(request, templatename="fellchensammlung/search.html"):
|
|
||||||
# A user just visiting the search site did not search, only upon completing the search form a user has really
|
# 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. This will toggle the "subscribe" button
|
||||||
searched = False
|
searched = False
|
||||||
@@ -266,7 +212,7 @@ def search(request, templatename="fellchensammlung/search.html"):
|
|||||||
"search_radius": search.max_distance,
|
"search_radius": search.max_distance,
|
||||||
"zoom_level": zoom_level_for_radius(search.max_distance),
|
"zoom_level": zoom_level_for_radius(search.max_distance),
|
||||||
"geocoding_api_url": settings.GEOCODING_API_URL, }
|
"geocoding_api_url": settings.GEOCODING_API_URL, }
|
||||||
return render(request, templatename, context=context)
|
return render(request, 'fellchensammlung/search.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -311,51 +257,6 @@ def add_adoption_notice(request):
|
|||||||
return render(request, 'fellchensammlung/forms/form_add_adoption.html', {'form': form})
|
return render(request, 'fellchensammlung/forms/form_add_adoption.html', {'form': form})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def add_adoption_notice_bulma(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
print("dada")
|
|
||||||
form = AdoptionNoticeFormWithDateWidgetAutoAnimal(request.POST)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
print("dodo")
|
|
||||||
an_instance = form.save(commit=False)
|
|
||||||
an_instance.owner = request.user
|
|
||||||
|
|
||||||
if request.user.trust_level >= TrustLevel.MODERATOR:
|
|
||||||
an_instance.set_active()
|
|
||||||
else:
|
|
||||||
an_instance.set_unchecked()
|
|
||||||
|
|
||||||
# Get the species and number of animals from the form
|
|
||||||
species = form.cleaned_data["species"]
|
|
||||||
sex = form.cleaned_data["sex"]
|
|
||||||
num_animals = form.cleaned_data["num_animals"]
|
|
||||||
date_of_birth = form.cleaned_data["date_of_birth"]
|
|
||||||
for i in range(0, num_animals):
|
|
||||||
Animal.objects.create(owner=request.user,
|
|
||||||
name=f"{species} {i + 1}", adoption_notice=an_instance, species=species, sex=sex,
|
|
||||||
date_of_birth=date_of_birth)
|
|
||||||
|
|
||||||
"""Log"""
|
|
||||||
Log.objects.create(user=request.user, action="add_adoption_notice",
|
|
||||||
text=f"{request.user} hat Vermittlung {an_instance.pk} hinzugefügt")
|
|
||||||
|
|
||||||
"""Spin up a task that adds the location and notifies search subscribers"""
|
|
||||||
post_adoption_notice_save.delay(an_instance.id)
|
|
||||||
|
|
||||||
"""Subscriptions"""
|
|
||||||
# Automatically subscribe user that created AN to AN
|
|
||||||
Subscriptions.objects.create(owner=request.user, adoption_notice=an_instance)
|
|
||||||
|
|
||||||
return redirect(reverse("adoption-notice-detail-bulma", args=[an_instance.pk]))
|
|
||||||
else:
|
|
||||||
print(form.errors)
|
|
||||||
else:
|
|
||||||
form = AdoptionNoticeFormWithDateWidgetAutoAnimal()
|
|
||||||
return render(request, 'fellchensammlung/forms/bulma-form-add-adoption.html', {'form': form})
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def adoption_notice_add_animal(request, adoption_notice_id):
|
def adoption_notice_add_animal(request, adoption_notice_id):
|
||||||
# Only users that are mods or owners of the adoption notice are allowed to add to it
|
# Only users that are mods or owners of the adoption notice are allowed to add to it
|
||||||
@@ -464,7 +365,15 @@ def animal_edit(request, animal_id):
|
|||||||
def about(request):
|
def about(request):
|
||||||
rules = Rule.objects.all()
|
rules = Rule.objects.all()
|
||||||
|
|
||||||
legal = i18n.get_texts_by_language(["terms_of_service", "privacy_statement", "imprint", "about_us", "faq"])
|
language_code = translation.get_language()
|
||||||
|
lang = Language.objects.get(languagecode=language_code)
|
||||||
|
|
||||||
|
legal = {}
|
||||||
|
for text_code in ["terms_of_service", "privacy_statement", "imprint", "about_us", "faq"]:
|
||||||
|
try:
|
||||||
|
legal[text_code] = Text.objects.get(text_code=text_code, language=lang, )
|
||||||
|
except Text.DoesNotExist:
|
||||||
|
legal[text_code] = None
|
||||||
|
|
||||||
context = {"rules": rules, }
|
context = {"rules": rules, }
|
||||||
context.update(legal)
|
context.update(legal)
|
||||||
@@ -475,47 +384,6 @@ def about(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def about_bulma(request):
|
|
||||||
context = i18n.get_texts_by_language(["about_us", "faq"])
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"fellchensammlung/bulma-about.html",
|
|
||||||
context=context
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def render_text(request, text):
|
|
||||||
context = {"text": text}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"fellchensammlung/bulma-one-text.html",
|
|
||||||
context=context
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def imprint(request):
|
|
||||||
text = i18n.get_text_by_language("imprint")
|
|
||||||
return render_text(request, text)
|
|
||||||
|
|
||||||
|
|
||||||
def privacy(request):
|
|
||||||
text = i18n.get_text_by_language("privacy_statement")
|
|
||||||
return render_text(request, text)
|
|
||||||
|
|
||||||
|
|
||||||
def terms_of_service(request):
|
|
||||||
text = i18n.get_text_by_language("terms_of_service")
|
|
||||||
rules = Rule.objects.all()
|
|
||||||
context = {"rules": rules, "text": text}
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"fellchensammlung/bulma-terms-of-service.html",
|
|
||||||
context=context
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def report_adoption(request, adoption_notice_id):
|
def report_adoption(request, adoption_notice_id):
|
||||||
"""
|
"""
|
||||||
Form to report adoption notices
|
Form to report adoption notices
|
||||||
@@ -560,13 +428,10 @@ def report_detail(request, report_id, form_complete=False):
|
|||||||
"""
|
"""
|
||||||
Detailed view of a report, including moderation actions
|
Detailed view of a report, including moderation actions
|
||||||
"""
|
"""
|
||||||
# Prefetching reduces the number of queries to the database that are needed (see reported_content)
|
report = Report.objects.get(pk=report_id)
|
||||||
report = Report.objects.select_related("reportadoptionnotice", "reportcomment").get(pk=report_id)
|
|
||||||
moderation_actions = ModerationAction.objects.filter(report_id=report_id)
|
moderation_actions = ModerationAction.objects.filter(report_id=report_id)
|
||||||
is_mod_or_above = user_is_trust_level_or_above(request.user, TrustLevel.MODERATOR)
|
|
||||||
|
|
||||||
context = {"report": report, "moderation_actions": moderation_actions,
|
context = {"report": report, "moderation_actions": moderation_actions, "form_complete": form_complete}
|
||||||
"form_complete": form_complete, "is_mod_or_above": is_mod_or_above}
|
|
||||||
|
|
||||||
return render(request, 'fellchensammlung/details/detail-report.html', context)
|
return render(request, 'fellchensammlung/details/detail-report.html', context)
|
||||||
|
|
||||||
@@ -637,7 +502,7 @@ def my_profile(request):
|
|||||||
|
|
||||||
@user_passes_test(user_is_trust_level_or_above)
|
@user_passes_test(user_is_trust_level_or_above)
|
||||||
def modqueue(request):
|
def modqueue(request):
|
||||||
open_reports = Report.objects.select_related("reportadoptionnotice", "reportcomment").filter(status=Report.WAITING)
|
open_reports = Report.objects.filter(status=Report.WAITING)
|
||||||
context = {"reports": open_reports}
|
context = {"reports": open_reports}
|
||||||
return render(request, 'fellchensammlung/modqueue.html', context=context)
|
return render(request, 'fellchensammlung/modqueue.html', context=context)
|
||||||
|
|
||||||
@@ -667,14 +532,10 @@ def updatequeue(request):
|
|||||||
return render(request, 'fellchensammlung/updatequeue.html', context=context)
|
return render(request, 'fellchensammlung/updatequeue.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def map(request, templatename='fellchensammlung/map.html'):
|
def map(request):
|
||||||
adoption_notices = AdoptionNotice.get_active_ANs()
|
adoption_notices = AdoptionNotice.get_active_ANs()
|
||||||
context = {"adoption_notices_map": adoption_notices}
|
context = {"adoption_notices_map": adoption_notices}
|
||||||
return render(request, templatename, context=context)
|
return render(request, 'fellchensammlung/map.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
def map_bulma(request):
|
|
||||||
return map(request, templatename='fellchensammlung/bulma-map.html')
|
|
||||||
|
|
||||||
|
|
||||||
def metrics(request):
|
def metrics(request):
|
||||||
@@ -771,28 +632,3 @@ def export_own_profile(request):
|
|||||||
ANs_as_json = serialize('json', ANs)
|
ANs_as_json = serialize('json', ANs)
|
||||||
full_json = f"{user_as_json}, {ANs_as_json}"
|
full_json = f"{user_as_json}, {ANs_as_json}"
|
||||||
return HttpResponse(full_json, content_type="application/json")
|
return HttpResponse(full_json, content_type="application/json")
|
||||||
|
|
||||||
|
|
||||||
def styleguide(request):
|
|
||||||
context = {"geocoding_api_url": settings.GEOCODING_API_URL, }
|
|
||||||
return render(request, 'fellchensammlung/styleguide.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
def styleguide_bulma(request):
|
|
||||||
return render(request, 'fellchensammlung/bulma-styleguide.html')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def rescue_organization_check(request):
|
|
||||||
if request.method == "POST":
|
|
||||||
rescue_org = RescueOrganization.objects.get(id=request.POST.get("rescue_organization_id"))
|
|
||||||
edit_permission = user_is_trust_level_or_above(request.user, TrustLevel.MODERATOR)
|
|
||||||
if not edit_permission:
|
|
||||||
return render(request, "fellchensammlung/errors/403.html", status=403)
|
|
||||||
action = request.POST.get("action")
|
|
||||||
if action == "checked":
|
|
||||||
rescue_org.set_checked()
|
|
||||||
|
|
||||||
last_checked_rescue_orgs = RescueOrganization.objects.order_by("last_checked")
|
|
||||||
context = {"rescue_orgs": last_checked_rescue_orgs, }
|
|
||||||
return render(request, 'fellchensammlung/rescue-organization-check.html', context=context)
|
|
||||||
|
@@ -89,9 +89,9 @@ CELERY_RESULT_BACKEND = config.get("celery", "backend", fallback="redis://localh
|
|||||||
HEALTHCHECKS_URL = config.get("monitoring", "healthchecks_url", fallback=None)
|
HEALTHCHECKS_URL = config.get("monitoring", "healthchecks_url", fallback=None)
|
||||||
|
|
||||||
""" GEOCODING """
|
""" GEOCODING """
|
||||||
GEOCODING_API_URL = config.get("geocoding", "api_url", fallback="https://photon.hyteck.de/api")
|
GEOCODING_API_URL = config.get("geocoding", "api_url", fallback="https://nominatim.hyteck.de/search")
|
||||||
# GEOCODING_API_FORMAT is allowed to be one of ['nominatim', 'photon']
|
# GEOCODING_API_FORMAT is allowed to be one of ['nominatim', 'photon']
|
||||||
GEOCODING_API_FORMAT = config.get("geocoding", "api_format", fallback="photon")
|
GEOCODING_API_FORMAT = config.get("geocoding", "api_format", fallback="nominatim")
|
||||||
|
|
||||||
""" Tile Server """
|
""" Tile Server """
|
||||||
MAP_TILE_SERVER = config.get("map", "tile_server", fallback="https://tiles.hyteck.de")
|
MAP_TILE_SERVER = config.get("map", "tile_server", fallback="https://tiles.hyteck.de")
|
||||||
@@ -168,7 +168,6 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
"django.contrib.sitemaps",
|
|
||||||
'fontawesomefree',
|
'fontawesomefree',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
"crispy_bootstrap4",
|
"crispy_bootstrap4",
|
||||||
@@ -176,7 +175,6 @@ INSTALLED_APPS = [
|
|||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
'drf_spectacular_sidecar', # required for Django collectstatic discovery
|
||||||
'widget_tweaks'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@@ -5,8 +5,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, TrustLevel, \
|
from fellchensammlung.models import Animal, Species, AdoptionNotice, User, Location, AdoptionNoticeStatus, TrustLevel, \
|
||||||
Animal, Subscriptions, Comment, CommentNotification, SearchSubscription
|
Animal, Subscriptions
|
||||||
from fellchensammlung.tools.geo import LocationProxy
|
|
||||||
from fellchensammlung.views import add_adoption_notice
|
from fellchensammlung.views import add_adoption_notice
|
||||||
|
|
||||||
|
|
||||||
@@ -147,35 +146,6 @@ class SearchTest(TestCase):
|
|||||||
self.assertContains(response, "TestAdoption3")
|
self.assertContains(response, "TestAdoption3")
|
||||||
self.assertNotContains(response, "TestAdoption2")
|
self.assertNotContains(response, "TestAdoption2")
|
||||||
|
|
||||||
def test_unauthenticated_subscribe(self):
|
|
||||||
response = self.client.post(reverse('search'), {"subscribe_to_search": ""})
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertEqual(response.url, "/accounts/login/?next=/suchen/")
|
|
||||||
|
|
||||||
def test_unauthenticated_unsubscribe(self):
|
|
||||||
response = self.client.post(reverse('search'), {"unsubscribe_to_search": 1})
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertEqual(response.url, "/accounts/login/?next=/suchen/")
|
|
||||||
|
|
||||||
def test_subscribe(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
response = self.client.post(reverse('search'), {"max_distance": 50, "location_string": "Berlin", "sex": "A",
|
|
||||||
"subscribe_to_search": ""})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertTrue(SearchSubscription.objects.filter(owner=User.objects.get(username='testuser0'),
|
|
||||||
max_distance=50).exists())
|
|
||||||
|
|
||||||
def test_unsubscribe(self):
|
|
||||||
user0 = User.objects.get(username='testuser0')
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
location = Location.get_location_from_string("München")
|
|
||||||
subscription = SearchSubscription.objects.create(owner=user0, max_distance=200, location=location, sex="A")
|
|
||||||
response = self.client.post(reverse('search'), {"max_distance": 200, "location_string": "München", "sex": "A",
|
|
||||||
"unsubscribe_to_search": subscription.pk})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFalse(SearchSubscription.objects.filter(owner=User.objects.get(username='testuser0'),
|
|
||||||
max_distance=200).exists())
|
|
||||||
|
|
||||||
def test_location_search(self):
|
def test_location_search(self):
|
||||||
response = self.client.post(reverse('search'), {"max_distance": 50, "location_string": "Berlin", "sex": "A"})
|
response = self.client.post(reverse('search'), {"max_distance": 50, "location_string": "Berlin", "sex": "A"})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
@@ -263,10 +233,10 @@ class AdoptionDetailTest(TestCase):
|
|||||||
password='12345')
|
password='12345')
|
||||||
test_user0.save()
|
test_user0.save()
|
||||||
|
|
||||||
cls.test_user1 = User.objects.create_user(username='testuser1',
|
test_user1 = User.objects.create_user(username='testuser1',
|
||||||
first_name="Max",
|
first_name="Max",
|
||||||
last_name="Müller",
|
last_name="Müller",
|
||||||
password='12345')
|
password='12345')
|
||||||
test_user0.trust_level = TrustLevel.ADMIN
|
test_user0.trust_level = TrustLevel.ADMIN
|
||||||
test_user0.save()
|
test_user0.save()
|
||||||
|
|
||||||
@@ -286,16 +256,6 @@ class AdoptionDetailTest(TestCase):
|
|||||||
adoption3.set_active()
|
adoption3.set_active()
|
||||||
adoption2.set_unchecked()
|
adoption2.set_unchecked()
|
||||||
|
|
||||||
def test_basic_view(self):
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('adoption-notice-detail', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)), )
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_basic_view_logged_in(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('adoption-notice-detail', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)), )
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_subscribe(self):
|
def test_subscribe(self):
|
||||||
self.client.login(username='testuser0', password='12345')
|
self.client.login(username='testuser0', password='12345')
|
||||||
@@ -304,6 +264,7 @@ class AdoptionDetailTest(TestCase):
|
|||||||
data={"action": "subscribe"})
|
data={"action": "subscribe"})
|
||||||
self.assertTrue(Subscriptions.objects.filter(owner__username="testuser0").exists())
|
self.assertTrue(Subscriptions.objects.filter(owner__username="testuser0").exists())
|
||||||
|
|
||||||
|
|
||||||
def test_unsubscribe(self):
|
def test_unsubscribe(self):
|
||||||
# Make sure subscription exists
|
# Make sure subscription exists
|
||||||
an = AdoptionNotice.objects.get(name="TestAdoption1")
|
an = AdoptionNotice.objects.get(name="TestAdoption1")
|
||||||
@@ -323,73 +284,3 @@ class AdoptionDetailTest(TestCase):
|
|||||||
data={"action": "subscribe"})
|
data={"action": "subscribe"})
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, "/accounts/login/?next=/vermittlung/1/")
|
self.assertEqual(response.url, "/accounts/login/?next=/vermittlung/1/")
|
||||||
|
|
||||||
def test_unauthenticated_comment(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('adoption-notice-detail', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)),
|
|
||||||
data={"action": "comment"})
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
def test_comment(self):
|
|
||||||
an1 = AdoptionNotice.objects.get(name="TestAdoption1")
|
|
||||||
# Set up subscription
|
|
||||||
Subscriptions.objects.create(owner=self.test_user1, adoption_notice=an1)
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('adoption-notice-detail', args=str(an1.pk)),
|
|
||||||
data={"action": "comment", "text": "Test"})
|
|
||||||
self.assertTrue(Comment.objects.filter(user__username="testuser0").exists())
|
|
||||||
self.assertFalse(CommentNotification.objects.filter(user__username="testuser0").exists())
|
|
||||||
self.assertTrue(CommentNotification.objects.filter(user__username="testuser1").exists())
|
|
||||||
|
|
||||||
|
|
||||||
class AdoptionEditTest(TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
test_user0 = User.objects.create_user(username='testuser0',
|
|
||||||
first_name="Admin",
|
|
||||||
last_name="BOFH",
|
|
||||||
password='12345')
|
|
||||||
test_user0.save()
|
|
||||||
|
|
||||||
cls.test_user1 = User.objects.create_user(username='testuser1',
|
|
||||||
first_name="Max",
|
|
||||||
last_name="Müller",
|
|
||||||
password='12345')
|
|
||||||
|
|
||||||
adoption1 = baker.make(AdoptionNotice, name="TestAdoption1", description="Test1", owner=test_user0)
|
|
||||||
adoption2 = baker.make(AdoptionNotice, name="TestAdoption2", description="Test2")
|
|
||||||
|
|
||||||
def test_basic_view(self):
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('adoption-notice-edit', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)), )
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
def test_basic_view_logged_in_unauthorized(self):
|
|
||||||
self.client.login(username='testuser1', password='12345')
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('adoption-notice-edit', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)), )
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
def test_basic_view_logged_in(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('adoption-notice-edit', args=str(AdoptionNotice.objects.get(name="TestAdoption1").pk)), )
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
def test_edit(self):
|
|
||||||
data = {"name": "Mia",
|
|
||||||
"searching_since": "01.01.2025",
|
|
||||||
"location_string": "Paderborn",
|
|
||||||
"organization": "",
|
|
||||||
"description": "Test3",
|
|
||||||
"further_information": ""}
|
|
||||||
an = AdoptionNotice.objects.get(name="TestAdoption1")
|
|
||||||
assert self.client.login(username='testuser0', password='12345')
|
|
||||||
response = self.client.post(reverse("adoption-notice-edit", args=str(an.pk)), data=data, follow=True)
|
|
||||||
self.assertEqual(response.redirect_chain[0][1],
|
|
||||||
302) # See https://docs.djangoproject.com/en/5.1/topics/testing/tools/
|
|
||||||
self.assertEqual(response.status_code, 200) # Redirects to AN page
|
|
||||||
self.assertContains(response, "Test3")
|
|
||||||
self.assertContains(response, "Mia")
|
|
||||||
self.assertNotContains(response, "Test1")
|
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from docs.conf import language
|
from fellchensammlung.models import User, TrustLevel, AdoptionNotice, Species
|
||||||
from fellchensammlung.models import User, TrustLevel, AdoptionNotice, Species, Rule, Language, Comment, ReportComment
|
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
|
||||||
|
|
||||||
@@ -27,19 +26,6 @@ class BasicViewTest(TestCase):
|
|||||||
for i in range(0, 4):
|
for i in range(0, 4):
|
||||||
AdoptionNotice.objects.get(name=f"TestAdoption{i}").set_active()
|
AdoptionNotice.objects.get(name=f"TestAdoption{i}").set_active()
|
||||||
|
|
||||||
rule1 = Rule.objects.create(title="Rule 1", rule_text="Description of r1", rule_identifier="rule1",
|
|
||||||
language=Language.objects.get(name="English"))
|
|
||||||
|
|
||||||
an1 = AdoptionNotice.objects.get(name="TestAdoption0")
|
|
||||||
comment1 = Comment.objects.create(adoption_notice=an1, text="Comment1", user=test_user1)
|
|
||||||
comment2 = Comment.objects.create(adoption_notice=an1, text="Comment2", user=test_user1)
|
|
||||||
comment3 = Comment.objects.create(adoption_notice=an1, text="Comment3", user=test_user1)
|
|
||||||
|
|
||||||
report_comment1 = ReportComment.objects.create(reported_comment=comment1,
|
|
||||||
user_comment="ReportComment1")
|
|
||||||
report_comment1.save()
|
|
||||||
report_comment1.reported_broken_rules.set({rule1,})
|
|
||||||
|
|
||||||
def test_index_logged_in(self):
|
def test_index_logged_in(self):
|
||||||
self.client.login(username='testuser0', password='12345')
|
self.client.login(username='testuser0', password='12345')
|
||||||
|
|
||||||
@@ -55,82 +41,3 @@ class BasicViewTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, "TestAdoption1")
|
self.assertContains(response, "TestAdoption1")
|
||||||
self.assertNotContains(response, "TestAdoption4") # Should not be active, therefore not shown
|
self.assertNotContains(response, "TestAdoption4") # Should not be active, therefore not shown
|
||||||
|
|
||||||
def test_about_logged_in(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
response = self.client.get(reverse('about'))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, "Rule 1")
|
|
||||||
|
|
||||||
def test_about_anonymous(self):
|
|
||||||
response = self.client.get(reverse('about'))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, "Rule 1")
|
|
||||||
|
|
||||||
def test_report_adoption_logged_in(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
an = AdoptionNotice.objects.get(name="TestAdoption0")
|
|
||||||
response = self.client.get(reverse('report-adoption-notice', args=str(an.pk)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
data = {"reported_broken_rules": 1, "user_comment": "animal cruelty"}
|
|
||||||
response = self.client.post(reverse('report-adoption-notice', args=str(an.pk)), data=data)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
def test_report_adoption_anonymous(self):
|
|
||||||
an = AdoptionNotice.objects.get(name="TestAdoption0")
|
|
||||||
response = self.client.get(reverse('report-adoption-notice', args=str(an.pk)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
data = {"reported_broken_rules": 1, "user_comment": "animal cruelty"}
|
|
||||||
response = self.client.post(reverse('report-adoption-notice', args=str(an.pk)), data=data)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
def test_report_comment_logged_in(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
c = Comment.objects.get(text="Comment1")
|
|
||||||
response = self.client.get(reverse('report-comment', args=str(c.pk)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
data = {"reported_broken_rules": 1, "user_comment": "animal cruelty"}
|
|
||||||
response = self.client.post(reverse('report-comment', args=str(c.pk)), data=data)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertTrue(ReportComment.objects.filter(reported_comment=c.pk).exists())
|
|
||||||
|
|
||||||
def test_report_comment_anonymous(self):
|
|
||||||
c = Comment.objects.get(text="Comment2")
|
|
||||||
response = self.client.get(reverse('report-comment', args=str(c.pk)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
data = {"reported_broken_rules": 1, "user_comment": "animal cruelty"}
|
|
||||||
response = self.client.post(reverse('report-comment', args=str(c.pk)), data=data)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertTrue(ReportComment.objects.filter(reported_comment=c.pk).exists())
|
|
||||||
|
|
||||||
def test_show_report_details_logged_in(self):
|
|
||||||
self.client.login(username='testuser1', password='12345')
|
|
||||||
report = ReportComment.objects.get(user_comment="ReportComment1")
|
|
||||||
response = self.client.get(reverse('report-detail', args=(report.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, "Rule 1")
|
|
||||||
self.assertContains(response, "ReportComment1")
|
|
||||||
self.assertNotContains(response, '<form action="allow" class="">')
|
|
||||||
|
|
||||||
def test_show_report_details_anonymous(self):
|
|
||||||
report = ReportComment.objects.get(user_comment="ReportComment1")
|
|
||||||
response = self.client.get(reverse('report-detail', args=(report.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, "Rule 1")
|
|
||||||
self.assertContains(response, "ReportComment1")
|
|
||||||
self.assertNotContains(response, '<form action="allow" class="">')
|
|
||||||
|
|
||||||
def test_show_report_details_admin(self):
|
|
||||||
self.client.login(username='testuser0', password='12345')
|
|
||||||
report = ReportComment.objects.get(user_comment="ReportComment1")
|
|
||||||
response = self.client.get(reverse('report-detail', args=(report.pk,)))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertContains(response, "Rule 1")
|
|
||||||
self.assertContains(response, "ReportComment1")
|
|
||||||
self.assertContains(response, '<form action="allow" class="">')
|
|
||||||
|
|
||||||
|
|
||||||
|