feat: Auto-add location to adoption notice
This commit is contained in:
		@@ -40,6 +40,7 @@ class AdoptionNoticeForm(forms.ModelForm):
 | 
				
			|||||||
                'name',
 | 
					                'name',
 | 
				
			||||||
                'group_only',
 | 
					                'group_only',
 | 
				
			||||||
                'searching_since',
 | 
					                'searching_since',
 | 
				
			||||||
 | 
					                'location_string',
 | 
				
			||||||
                'description',
 | 
					                'description',
 | 
				
			||||||
                'further_information',
 | 
					                'further_information',
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -47,7 +48,7 @@ class AdoptionNoticeForm(forms.ModelForm):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = AdoptionNotice
 | 
					        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):
 | 
					class AnimalForm(forms.ModelForm):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -66,19 +66,11 @@ class Location(models.Model):
 | 
				
			|||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return f"{self.name}"
 | 
					        return f"{self.name}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    GERMANY = "DE"
 | 
					    place_id = models.IntegerField()
 | 
				
			||||||
    AUSTRIA = "AT"
 | 
					    osm_id = models.IntegerField()
 | 
				
			||||||
    SWITZERLAND = "CH"
 | 
					    latitude = models.FloatField()
 | 
				
			||||||
    COUNTRIES_CHOICES = {
 | 
					    longitude = models.FloatField()
 | 
				
			||||||
        GERMANY: "Germany",
 | 
					    name = models.CharField(max_length=2000)
 | 
				
			||||||
        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'))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RescueOrganization(models.Model):
 | 
					class RescueOrganization(models.Model):
 | 
				
			||||||
@@ -87,6 +79,7 @@ class RescueOrganization(models.Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    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'))
 | 
				
			||||||
 | 
					    location_string = models.CharField(max_length=200, verbose_name=_("Ort der Organisation"))
 | 
				
			||||||
    location = models.ForeignKey(Location, on_delete=models.PROTECT)
 | 
					    location = models.ForeignKey(Location, on_delete=models.PROTECT)
 | 
				
			||||||
    instagram = models.URLField(null=True, blank=True, verbose_name=_('Instagram Profil'))
 | 
					    instagram = models.URLField(null=True, blank=True, verbose_name=_('Instagram Profil'))
 | 
				
			||||||
    facebook = models.URLField(null=True, blank=True, verbose_name=_('Facebook 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'))
 | 
					    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'))
 | 
					    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 = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def animals(self):
 | 
					    def animals(self):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import requests
 | 
					import requests
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
from notfellchen import __version__ as nf_version
 | 
					from notfellchen import __version__ as nf_version
 | 
				
			||||||
 | 
					from fellchensammlung.models import Location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from math import radians, sqrt, sin, cos, atan2
 | 
					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_lat = radians(latitude2 - latitude1)
 | 
				
			||||||
    distance_long = radians(longitude2 - longitude1)
 | 
					    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))
 | 
					    c = 2 * atan2(sqrt(a), sqrt(1 - a))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    distance_in_km = earth_radius_km * c
 | 
					    distance_in_km = earth_radius_km * c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return distance_in_km
 | 
					    return distance_in_km
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ResponseMock:
 | 
					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"]}]'
 | 
					    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
 | 
					    status_code = 200
 | 
				
			||||||
@@ -43,21 +46,33 @@ class RequestMock:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GeoAPI:
 | 
					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):
 | 
					    def __init__(self, debug=True):
 | 
				
			||||||
        self.api_url = "https://nominatim.openstreetmap.org/search"
 | 
					 | 
				
			||||||
        if debug:
 | 
					        if debug:
 | 
				
			||||||
            self.requests = RequestMock
 | 
					            self.requests = RequestMock
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.requests = requests
 | 
					            self.requests = requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_coordinates_from_postcode(self, postcode):
 | 
					    def get_coordinates_from_postcode(self, postcode):
 | 
				
			||||||
        headers = {
 | 
					        result = self.requests.get(self.api_url, {"q": postcode, "format": "jsonv2"}, headers=self.headers).json()[0]
 | 
				
			||||||
            '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]
 | 
					 | 
				
			||||||
        return result["lat"], result["lon"]
 | 
					        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__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    geo = GeoAPI(debug=True)
 | 
					    geo = GeoAPI(debug=True)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ urlpatterns = [
 | 
				
			|||||||
    # ex: /search/
 | 
					    # ex: /search/
 | 
				
			||||||
    path("suchen/", views.search, name="search"),
 | 
					    path("suchen/", views.search, name="search"),
 | 
				
			||||||
    # ex: /vermitteln/
 | 
					    # 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"),
 | 
					    path("ueber-uns/", views.about, name="about"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ from fellchensammlung.models import AdoptionNotice, Text, Animal, Rule, Image, R
 | 
				
			|||||||
    Member
 | 
					    Member
 | 
				
			||||||
from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm
 | 
					from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm
 | 
				
			||||||
from .models import Language, Announcement
 | 
					from .models import Language, Announcement
 | 
				
			||||||
 | 
					from .tools.geo import GeoAPI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def index(request):
 | 
					def index(request):
 | 
				
			||||||
@@ -93,12 +94,19 @@ def search(request):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def add_adoption(request):
 | 
					def add_adoption_notice(request):
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
        form = AdoptionNoticeForm(request.POST, request.FILES, in_adoption_notice_creation_flow=True)
 | 
					        form = AdoptionNoticeForm(request.POST, request.FILES, in_adoption_notice_creation_flow=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if form.is_valid():
 | 
					        if form.is_valid():
 | 
				
			||||||
            instance = form.save()
 | 
					            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]))
 | 
					            return redirect(reverse("adoption-notice-add-animal", args=[instance.pk]))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        form = AdoptionNoticeForm(in_adoption_notice_creation_flow=True)
 | 
					        form = AdoptionNoticeForm(in_adoption_notice_creation_flow=True)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user