Compare commits
18 Commits
ci-test-co
...
712c3d32f3
Author | SHA1 | Date | |
---|---|---|---|
712c3d32f3 | |||
8998bbdf6d | |||
ff31caa139 | |||
ad06829c31 | |||
03a48da355 | |||
885bed888d | |||
0051cb07c9 | |||
8858cff9cf | |||
70e2af6172 | |||
461abd2e46 | |||
![]() |
d7269106db | ||
77fb99a527 | |||
38a56daa24 | |||
![]() |
ac0749797f | ||
f193f7d7ca | |||
43657e0862 | |||
68ad366f74 | |||
350d2c5da9 |
97
scripts/upload_animal_shelters.py
Normal file
97
scripts/upload_animal_shelters.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
DEFAULT_OSM_DATA_FILE = "osm_data.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 load_osm_data(file_path):
|
||||||
|
"""Load OSM data from a GeoJSON file."""
|
||||||
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
|
data = json.load(file)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def load_osm_data(file_path):
|
||||||
|
#Load OSM data from a GeoJSON file.
|
||||||
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
|
data = json.load(file)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def transform_osm_data(feature):
|
||||||
|
#Transform a single OSM feature into the API payload format
|
||||||
|
prop = feature.get("properties", {})
|
||||||
|
geometry = feature.get("geometry", {})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": prop.get("name", "Unnamed Shelter"),
|
||||||
|
"phone": prop.get("phone"),
|
||||||
|
"website": prop.get("website"),
|
||||||
|
"opening_hours": prop.get("opening_hours"),
|
||||||
|
"email": prop.get("email"),
|
||||||
|
"location_string": f'{prop.get("addr:street", "")} {prop.get("addr:housenumber", "")} {prop.get("addr:postcode", "")} {prop.get("addr:city", "")}',
|
||||||
|
"external_object_id": prop.get("@id"),
|
||||||
|
"external_source_id": "OSM"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def send_to_api(data, endpoint, headers):
|
||||||
|
# Send transformed data to the Notfellchen API.
|
||||||
|
response = requests.post(endpoint, headers=headers, json=data)
|
||||||
|
if response.status_code == 201:
|
||||||
|
print(f"Success: Shelter '{data['name']}' uploaded.")
|
||||||
|
elif response.status_code == 400:
|
||||||
|
print(f"Error: Shelter '{data['name']}' already exists or invalid data. {response.text}")
|
||||||
|
else:
|
||||||
|
print(f"Unexpected Error: {response.status_code} - {response.text}")
|
||||||
|
raise ConnectionError
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Get configuration
|
||||||
|
api_token, instance, data_file = get_config()
|
||||||
|
|
||||||
|
# Set headers and endpoint
|
||||||
|
endpoint = f"{instance}/api/organizations/"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Token {api_token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 1: Load OSM data
|
||||||
|
osm_data = load_osm_data(data_file)
|
||||||
|
|
||||||
|
# Step 2: Process each shelter and send it to the API
|
||||||
|
for feature in osm_data.get("features", []):
|
||||||
|
shelter_data = transform_osm_data(feature)
|
||||||
|
send_to_api(shelter_data, endpoint, headers)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@@ -16,6 +16,7 @@ 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]
|
||||||
|
|
||||||
@@ -84,7 +85,6 @@ class AdoptionNoticeApiView(APIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AnimalApiView(APIView):
|
class AnimalApiView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@@ -118,6 +118,7 @@ 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]
|
||||||
|
|
||||||
@@ -159,13 +160,14 @@ 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(owner=request.user)
|
rescue_org = serializer.save()
|
||||||
return Response(
|
return Response(
|
||||||
{"message": "Rescue organization created/updated 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)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class AddImageApiView(APIView):
|
class AddImageApiView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
47
src/fellchensammlung/sitemap.py
Normal file
47
src/fellchensammlung/sitemap.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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
|
7
src/fellchensammlung/static/robots.txt
Normal file
7
src/fellchensammlung/static/robots.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /admin/
|
||||||
|
|
||||||
|
User-agent: OpenAI
|
||||||
|
Disallow: /
|
||||||
|
|
||||||
|
Sitemap: https://notfellchen.org/sitemap.xml
|
102
src/fellchensammlung/templates/fellchensammlung/styleguide.html
Normal file
102
src/fellchensammlung/templates/fellchensammlung/styleguide.html
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{% 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">
|
||||||
|
<ul id="results"></ul>
|
||||||
|
<div class="container-edit-buttons">
|
||||||
|
<button class="btn" type="submit" value="search" name="search">
|
||||||
|
<i class="fas fa-search"></i> {% trans 'Suchen' %}
|
||||||
|
</button>
|
||||||
|
{% if searched %}
|
||||||
|
{% if subscribed_search %}
|
||||||
|
<button class="btn" 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="btn" type="submit" name="subscribe_to_search">
|
||||||
|
<i class="fas fa-bell"></i> {% trans 'Suche abonnieren' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if place_not_found %}
|
||||||
|
<p class="error">
|
||||||
|
{% trans 'Ort nicht gefunden' %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
<div class="card half">
|
||||||
|
{% include "fellchensammlung/partials/partial-map.html" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "fellchensammlung/lists/list-adoption-notices.html" %}
|
||||||
|
|
||||||
|
<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=de`);
|
||||||
|
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 %}
|
@@ -7,6 +7,15 @@ 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("rss/", LatestAdoptionNoticesFeed(), name="rss"),
|
path("rss/", LatestAdoptionNoticesFeed(), name="rss"),
|
||||||
@@ -95,4 +104,10 @@ 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"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@@ -2,6 +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
|
||||||
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
|
||||||
@@ -133,7 +134,7 @@ def adoption_notice_detail(request, adoption_notice_id):
|
|||||||
elif action == "subscribe":
|
elif action == "subscribe":
|
||||||
return redirect_to_login(next=request.path)
|
return redirect_to_login(next=request.path)
|
||||||
else:
|
else:
|
||||||
raise PermissionDenied
|
return HttpResponseForbidden()
|
||||||
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,
|
||||||
@@ -632,3 +633,9 @@ 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)
|
||||||
|
@@ -168,6 +168,7 @@ 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",
|
||||||
|
@@ -5,7 +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
|
Animal, Subscriptions, Comment, CommentNotification
|
||||||
from fellchensammlung.views import add_adoption_notice
|
from fellchensammlung.views import add_adoption_notice
|
||||||
|
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ class AdoptionDetailTest(TestCase):
|
|||||||
password='12345')
|
password='12345')
|
||||||
test_user0.save()
|
test_user0.save()
|
||||||
|
|
||||||
test_user1 = User.objects.create_user(username='testuser1',
|
cls.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')
|
||||||
@@ -256,6 +256,16 @@ 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')
|
||||||
@@ -264,7 +274,6 @@ 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")
|
||||||
@@ -284,3 +293,72 @@ 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")
|
||||||
|
Reference in New Issue
Block a user