feat: Auto-add location to adoption notice

This commit is contained in:
moanos [he/him] 2024-05-31 10:58:57 +02:00
parent c8f65c9c54
commit 2a9dc337d2
6 changed files with 123 additions and 23 deletions

View File

@ -40,6 +40,7 @@ class AdoptionNoticeForm(forms.ModelForm):
'name',
'group_only',
'searching_since',
'location_string',
'description',
'further_information',
),
@ -47,7 +48,7 @@ class AdoptionNoticeForm(forms.ModelForm):
class Meta:
model = AdoptionNotice
fields = ['name', "group_only", "further_information", "description", "searching_since"]
fields = ['name', "group_only", "further_information", "description", "searching_since", "location_string"]
class AnimalForm(forms.ModelForm):

View File

@ -0,0 +1,81 @@
# Generated by Django 5.0.6 on 2024-06-05 13:19
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("fellchensammlung", "0006_announcement_type"),
]
operations = [
migrations.RemoveField(
model_name="location",
name="country",
),
migrations.RemoveField(
model_name="location",
name="description",
),
migrations.RemoveField(
model_name="location",
name="postcode",
),
migrations.AddField(
model_name="adoptionnotice",
name="location",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="fellchensammlung.location",
),
),
migrations.AddField(
model_name="adoptionnotice",
name="location_string",
field=models.CharField(
default="72072", max_length=200, verbose_name="Ortsangabe"
),
preserve_default=False,
),
migrations.AddField(
model_name="location",
name="latitude",
field=models.FloatField(default=47),
preserve_default=False,
),
migrations.AddField(
model_name="location",
name="longitude",
field=models.FloatField(default=9),
preserve_default=False,
),
migrations.AddField(
model_name="location",
name="osm_id",
field=models.IntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name="location",
name="place_id",
field=models.IntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name="rescueorganization",
name="location_string",
field=models.CharField(
default="72072", max_length=200, verbose_name="Ort der Organisation"
),
preserve_default=False,
),
migrations.AlterField(
model_name="location",
name="name",
field=models.CharField(max_length=2000),
),
]

View File

@ -66,19 +66,11 @@ class Location(models.Model):
def __str__(self):
return f"{self.name}"
GERMANY = "DE"
AUSTRIA = "AT"
SWITZERLAND = "CH"
COUNTRIES_CHOICES = {
GERMANY: "Germany",
AUSTRIA: "Austria",
SWITZERLAND: "Switzerland"
}
name = models.CharField(max_length=200)
postcode = models.CharField(max_length=200)
country = models.CharField(max_length=20, choices=COUNTRIES_CHOICES)
description = models.TextField(null=True, blank=True, verbose_name=_('Beschreibung'))
place_id = models.IntegerField()
osm_id = models.IntegerField()
latitude = models.FloatField()
longitude = models.FloatField()
name = models.CharField(max_length=2000)
class RescueOrganization(models.Model):
@ -87,6 +79,7 @@ class RescueOrganization(models.Model):
name = models.CharField(max_length=200)
trusted = models.BooleanField(default=False, verbose_name=_('Vertrauenswürdig'))
location_string = models.CharField(max_length=200, verbose_name=_("Ort der Organisation"))
location = models.ForeignKey(Location, on_delete=models.PROTECT)
instagram = models.URLField(null=True, blank=True, verbose_name=_('Instagram Profil'))
facebook = models.URLField(null=True, blank=True, verbose_name=_('Facebook Profil'))
@ -112,6 +105,8 @@ class AdoptionNotice(models.Model):
further_information = models.URLField(null=True, blank=True, verbose_name=_('Link zu mehr Informationen'))
group_only = models.BooleanField(default=False, verbose_name=_('Ausschließlich Gruppenadoption'))
photos = models.ManyToManyField(Image, blank=True)
location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe"))
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL,)
@property
def animals(self):

View File

@ -1,6 +1,7 @@
import requests
import json
from notfellchen import __version__ as nf_version
from fellchensammlung.models import Location
from math import radians, sqrt, sin, cos, atan2
@ -21,13 +22,15 @@ def calculate_distance_between_coordinates(position1, position2):
distance_lat = radians(latitude2 - latitude1)
distance_long = radians(longitude2 - longitude1)
a = pow(sin(distance_lat / 2), 2) + cos(radians(latitude1)) * cos(radians(latitude2)) * pow(sin(distance_long / 2), 2)
a = pow(sin(distance_lat / 2), 2) + cos(radians(latitude1)) * cos(radians(latitude2)) * pow(sin(distance_long / 2),
2)
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance_in_km = earth_radius_km * c
return distance_in_km
class ResponseMock:
content = b'[{"place_id":138181499,"licence":"Data \xc2\xa9 OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":1247237,"lat":"48.4949904","lon":"9.040330235970146","category":"boundary","type":"postal_code","place_rank":21, "importance":0.12006895017929346,"addresstype":"postcode","name":"72072","display_name":"72072, Derendingen, T\xc3\xbcbingen, Landkreis T\xc3\xbcbingen, Baden-W\xc3\xbcrttemberg, Deutschland", "boundingbox":["48.4949404","48.4950404","9.0402802","9.0403802"]}]'
status_code = 200
@ -43,21 +46,33 @@ class RequestMock:
class GeoAPI:
api_url = "https://nominatim.openstreetmap.org/search"
headers = {
'User-Agent': f"Notfellchen {nf_version}",
'From': 'info@notfellchen.org' # This is another valid field
}
def __init__(self, debug=True):
self.api_url = "https://nominatim.openstreetmap.org/search"
if debug:
self.requests = RequestMock
else:
self.requests = requests
def get_coordinates_from_postcode(self, postcode):
headers = {
'User-Agent': f"Notfellchen {nf_version}",
'From': 'info@notfellchen.org' # This is another valid field
}
result = self.requests.get(self.api_url, {"q": postcode, "format": "jsonv2"}, headers=headers).json()[0]
result = self.requests.get(self.api_url, {"q": postcode, "format": "jsonv2"}, headers=self.headers).json()[0]
return result["lat"], result["lon"]
def get_location_from_string(self, location_string):
result = self.requests.get(self.api_url, {"q": location_string, "format": "jsonv2"}, headers=self.headers).json()[0]
location = Location.objects.create(
place_id=result["place_id"],
osm_id=result["osm_id"],
latitude=result["lat"],
longitude=result["lon"],
name=result["name"],
)
return location
if __name__ == "__main__":
geo = GeoAPI(debug=True)

View File

@ -27,7 +27,7 @@ urlpatterns = [
# ex: /search/
path("suchen/", views.search, name="search"),
# ex: /vermitteln/
path("vermitteln/", views.add_adoption, name="add-adoption"),
path("vermitteln/", views.add_adoption_notice, name="add-adoption"),
path("ueber-uns/", views.about, name="about"),

View File

@ -15,6 +15,7 @@ from fellchensammlung.models import AdoptionNotice, Text, Animal, Rule, Image, R
Member
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm
from .models import Language, Announcement
from .tools.geo import GeoAPI
def index(request):
@ -93,12 +94,19 @@ def search(request):
@login_required
def add_adoption(request):
def add_adoption_notice(request):
if request.method == 'POST':
form = AdoptionNoticeForm(request.POST, request.FILES, in_adoption_notice_creation_flow=True)
if form.is_valid():
instance = form.save()
"""Search the location given in the location string and add it to the adoption notice"""
geo_api = GeoAPI(debug=True)
location = geo_api.get_location_from_string(instance.location_string)
instance.location = location
instance.save()
return redirect(reverse("adoption-notice-add-animal", args=[instance.pk]))
else:
form = AdoptionNoticeForm(in_adoption_notice_creation_flow=True)