From 399ecf73ad354cc3778bd18eaf5a7c78479db304 Mon Sep 17 00:00:00 2001 From: moanos Date: Tue, 31 Dec 2024 15:40:33 +0100 Subject: [PATCH] feat: use location proxy to make Location search interface more intuitive --- src/fellchensammlung/models.py | 29 ++++++++++++++---------- src/fellchensammlung/tools/geo.py | 33 +++++++++++++++++++++++++++- src/fellchensammlung/tools/search.py | 25 +++++++++++---------- 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/fellchensammlung/models.py b/src/fellchensammlung/models.py index 8fcaac6..b6f0385 100644 --- a/src/fellchensammlung/models.py +++ b/src/fellchensammlung/models.py @@ -11,6 +11,7 @@ from django.contrib.auth.models import AbstractUser from .tools import misc, geo from notfellchen.settings import MEDIA_URL +from .tools.geo import LocationProxy class Language(models.Model): @@ -45,26 +46,30 @@ class Location(models.Model): def __str__(self): return f"{self.name} ({self.latitude:.5}, {self.longitude:.5})" + @property + def position(self): + return (self.latitude, self.longitude) + @property def str_hr(self): return f"{self.name.split(',')[0]}" @staticmethod def get_location_from_string(location_string): - geo_api = geo.GeoAPI() - geojson = geo_api.get_geojson_for_query(location_string) - if geojson is None: + try: + proxy = LocationProxy(location_string) + except ValueError: return None - result = geojson[0] - if "name" in result: - name = result["name"] - else: - name = result["display_name"] + location = Location.get_location_from_proxy(proxy) + return location + + @staticmethod + def get_location_from_proxy(proxy): location = Location.objects.create( - place_id=result["place_id"], - latitude=result["lat"], - longitude=result["lon"], - name=name, + place_id=proxy.place_id, + latitude=proxy.latitude, + longitude=proxy.longitude, + name=proxy.name, ) return location diff --git a/src/fellchensammlung/tools/geo.py b/src/fellchensammlung/tools/geo.py index dad98df..85fa68b 100644 --- a/src/fellchensammlung/tools/geo.py +++ b/src/fellchensammlung/tools/geo.py @@ -65,7 +65,8 @@ class GeoAPI: def get_coordinates_from_query(self, location_string): try: result = \ - self.requests.get(self.api_url, {"q": location_string, "format": "jsonv2"}, headers=self.headers).json()[0] + self.requests.get(self.api_url, {"q": location_string, "format": "jsonv2"}, + headers=self.headers).json()[0] except IndexError: return None return result["lat"], result["lon"] @@ -89,6 +90,36 @@ class GeoAPI: return result +class LocationProxy: + """ + Location proxy is used as a precursor to the location model without the need to create unnecessary database objects + """ + + def __init__(self, location_string): + """ + Creates the location proxy from the location string + """ + self.geo_api = GeoAPI() + geojson = self.geo_api.get_geojson_for_query(location_string) + if geojson is None: + raise ValueError + result = geojson[0] + if "name" in result: + self.name = result["name"] + else: + self.name = result["display_name"] + self.place_id = result["place_id"] + self.latitude = result["lat"] + self.longitude = result["lon"] + + def __eq__(self, other): + return self.place_id == other.place_id + + @property + def position(self): + return (self.latitude, self.longitude) + + if __name__ == "__main__": geo = GeoAPI(debug=False) print(geo.get_coordinates_from_query("12101")) diff --git a/src/fellchensammlung/tools/search.py b/src/fellchensammlung/tools/search.py index 1c2efcb..82868f3 100644 --- a/src/fellchensammlung/tools/search.py +++ b/src/fellchensammlung/tools/search.py @@ -1,7 +1,7 @@ import logging from django.utils.translation import gettext_lazy as _ -from .geo import GeoAPI +from .geo import GeoAPI, LocationProxy from ..forms import AdoptionNoticeSearchForm from ..models import SearchSubscription, AdoptionNotice, AdoptionNoticeNotification, SexChoicesWithAll, Location @@ -21,16 +21,20 @@ def notify_search_subscribers(adoption_notice: AdoptionNotice): class Search: - def __init__(self): + def __init__(self, request=None, search_subscription=None): self.sex = None self.area_search = None self.max_distance = None - self.location_string = None - self.search_position = None self.location = None self.place_not_found = False # Indicates that a location was given but could not be geocoded self.search_form = None + if request: + self.search_from_request(request) + elif search_subscription: + self.search_from_search_subscription(search_subscription) + + def __str__(self): return f"Search: {self.sex=}, {self.location=}, {self.search_position=}, {self.area_search=}, {self.max_distance=}" @@ -57,7 +61,7 @@ class Search: # make sure it's an area search and the place is found to check location if self.area_search and not self.place_not_found: # If adoption notice is in not in search distance, return false - if not adoption_notice.in_distance(self.search_position, self.max_distance): + if not adoption_notice.in_distance(self.location.position, self.max_distance): return False return True @@ -82,9 +86,9 @@ class Search: self.location_string = self.search_form.cleaned_data["location_string"] self.max_distance = int(self.search_form.cleaned_data["max_distance"]) - geo_api = GeoAPI() - self.search_position = geo_api.get_coordinates_from_query(self.location_string) - if self.search_position is None: + try: + self.location = LocationProxy(self.location_string) + except ValueError: self.place_not_found = True else: self.search_form = AdoptionNoticeSearchForm() @@ -94,7 +98,6 @@ class Search: search = Search() search.sex = search_subscription.sex search.location = search_subscription.location - search.search_position = (search_subscription.location.latitude, search_subscription.location.longitude) search.area_search = True search.max_distance = search_subscription.max_distance @@ -104,7 +107,8 @@ class Search: def subscribe(self, user): logging.info(f"{user} subscribed to search") - self._locate() + if isinstance(self.location, LocationProxy): + self.location = Location.get_location_from_proxy(self.location) SearchSubscription.objects.create(owner=user, location=self.location, sex=self.sex, @@ -115,7 +119,6 @@ class Search: Returns true if a user is already subscribed to a search with these parameters """ user_subscriptions = SearchSubscription.objects.filter(owner=user) - self._locate() for subscription in user_subscriptions: if self == subscription: return True