diff --git a/qgisfeedproject/qgisfeed/tests.py b/qgisfeedproject/qgisfeed/tests.py index b72ed98..0aed7a9 100644 --- a/qgisfeedproject/qgisfeed/tests.py +++ b/qgisfeedproject/qgisfeed/tests.py @@ -133,21 +133,6 @@ def test_lang_filter(self): self.assertTrue("Null Island QGIS Meeting" in titles) self.assertTrue("QGIS acquired by ESRI" in titles) - def test_lat_lon_filter(self): - c = Client(HTTP_USER_AGENT='Mozilla/5.0 QGIS/32400/Fedora ' - 'Linux (Workstation Edition)') - response = c.get('/?lat=0&lon=0') - data = json.loads(response.content) - titles = [d['title'] for d in data] - self.assertTrue("Null Island QGIS Meeting" in titles) - self.assertFalse("QGIS Italian Meeting" in titles) - - response = c.get('/?lat=44.5&lon=9.5') - data = json.loads(response.content) - titles = [d['title'] for d in data] - self.assertFalse("Null Island QGIS Meeting" in titles) - self.assertTrue("QGIS Italian Meeting" in titles) - def test_after(self): c = Client(HTTP_USER_AGENT='Mozilla/5.0 QGIS/32400/Fedora ' 'Linux (Workstation Edition)') @@ -177,8 +162,6 @@ def test_after(self): def test_invalid_parameters(self): c = Client(HTTP_USER_AGENT='Mozilla/5.0 QGIS/32400/Fedora ' 'Linux (Workstation Edition)') - response = c.get('/?lat=ZZ&lon=KK') - self.assertEqual(response.status_code, 400) response = c.get('/?lang=KK') self.assertEqual(response.status_code, 400) response = c.get('/?lang=english') @@ -413,6 +396,17 @@ def test_feeds_list_filtering(self): self.assertTrue('current_order' in response.context) self.assertTrue('form' in response.context) self.assertTrue('count' in response.context) + + def test_geofence_feature(self): + c = Client( + HTTP_USER_AGENT='Mozilla/5.0 QGIS/32400/Windows 10', + REMOTE_ADDR='180.247.213.160' + ) + response = c.get('/') + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'QGIS Italian Meeting') + self.assertNotContains(response, 'Null Island QGIS Meeting') + self.assertContains(response, 'QGIS acquired by ESRI') class FeedsItemFormTestCase(TestCase): """ diff --git a/qgisfeedproject/qgisfeed/utils.py b/qgisfeedproject/qgisfeed/utils.py index c8832a1..145f30b 100644 --- a/qgisfeedproject/qgisfeed/utils.py +++ b/qgisfeedproject/qgisfeed/utils.py @@ -7,6 +7,8 @@ from django.core.mail import EmailMultiAlternatives from django.core.mail import send_mail from django.contrib.gis.db.models import Model +from django.http import HttpRequest +from django.contrib.gis.geoip2 import GeoIP2 logger = logging.getLogger('qgisfeed.admin') QGISFEED_FROM_EMAIL = getattr(settings, 'QGISFEED_FROM_EMAIL', 'noreply@qgis.org') @@ -43,3 +45,27 @@ def get_field_max_length(ConfigurationModel: Model, field_name: str): return config.max_characters except ConfigurationModel.DoesNotExist: return 500 + + +def parse_remote_addr(request: HttpRequest) -> str: + """Extract client IP from request.""" + x_forwarded_for = request.headers.get("X-Forwarded-For", "") + if x_forwarded_for: + return x_forwarded_for.split(",")[0] + return request.META.get("REMOTE_ADDR", "") + +def get_location(remote_addr: str) -> str: + """ + Return WKT location for the given remote_addr. + This should be used only for the geofence feature + and won't be saved in the database. + """ + g = GeoIP2() + if remote_addr: + try: + location = g.city(remote_addr) + location_wkt = f"POINT({location['longitude']} {location['latitude']})" + return location_wkt + except Exception as e: + return None + return None \ No newline at end of file diff --git a/qgisfeedproject/qgisfeed/views.py b/qgisfeedproject/qgisfeed/views.py index 1366d34..832d7e7 100644 --- a/qgisfeedproject/qgisfeed/views.py +++ b/qgisfeedproject/qgisfeed/views.py @@ -29,7 +29,7 @@ from django.contrib.auth.models import User from .forms import FeedEntryFilterForm, FeedItemForm, HomePageFilterForm -from .utils import get_field_max_length, notify_reviewers +from .utils import get_field_max_length, notify_reviewers, parse_remote_addr, get_location from .models import QgisFeedEntry, CharacterLimitConfiguration from .languages import LANGUAGE_KEYS import json @@ -79,13 +79,16 @@ def get_filters(self, request): else: raise BadRequestException("Invalid language parameter.") filters['lang'] = lang - if request.GET.get('lat') and request.GET.get('lon'): - try: - location = 'point(%s %s)' % (request.GET.get('lon'), request.GET.get('lat')) + + # Build location filter from the request's remote address + # as QGIS doesn't send the location in the request + # This is used to filter entries by location + remote_addr = parse_remote_addr(request) + if remote_addr: + location = get_location(remote_addr) + if location: GEOSGeometry(location) filters['location'] = location - except ValueError: - raise BadRequestException("Invalid lat/lon parameters.") if request.GET.get('publish_from'): try: @@ -152,7 +155,9 @@ def get(self, request): qs = qs.filter(Q(language_filter__isnull=True) | Q(language_filter=filters.get('lang'))) if filters.get('location') is not None: - qs = qs.filter(spatial_filter__contains=filters.get('location')) + qs = qs.filter( + Q(spatial_filter__contains=filters.get('location')) | Q(spatial_filter__isnull=True) + ) for record in qs.values('pk', 'publish_from', 'publish_to', 'title','image', 'content', 'url', 'sticky')[:QGISFEED_MAX_RECORDS]: if record['publish_from']: