3
3
4
4
import requests
5
5
from celery import shared_task
6
+ from django .contrib .gis .geos import Point
6
7
from django .core .cache import cache
7
8
from django .db import transaction
8
9
from django .utils .translation import gettext as _
@@ -161,18 +162,23 @@ def _need_whois_lookup(self, new_ip):
161
162
162
163
The lookup is not triggered if:
163
164
- The new IP address is None or it is a private IP address.
164
- - The WHOIS information of new ip is already present.
165
- - WHOIS is disabled in the organization settings. (query from db)
165
+ - The WhoIs information of new ip is already present.
166
+ - WhoIs is disabled in the organization settings. (query from db)
167
+
168
+ Two boolean values are returned:
169
+ - First boolean indicates if WhoIs lookup is needed.
170
+ - Second boolean indicates if WhoIs info already exists in the db,
171
+ which is used for managing fuzzy locations.
166
172
"""
167
173
168
174
# Check cheap conditions first before hitting the database
169
175
if not self .is_valid_public_ip_address (new_ip ):
170
- return False
176
+ return False , False
171
177
172
178
if self ._get_whois_info_from_db (new_ip ).exists ():
173
- return False
179
+ return False , True
174
180
175
- return self .is_whois_enabled
181
+ return self .is_whois_enabled , False
176
182
177
183
def get_device_whois_info (self ):
178
184
"""
@@ -190,14 +196,20 @@ def trigger_whois_lookup(self):
190
196
Trigger WhoIs lookup based on the conditions of `_need_whois_lookup`.
191
197
Task is triggered on commit to ensure redundant data is not created.
192
198
"""
193
- if self ._need_whois_lookup (self .device .last_ip ):
199
+
200
+ fetch_whois , whois_info_exists = self ._need_whois_lookup (self .device .last_ip )
201
+ if fetch_whois :
194
202
transaction .on_commit (
195
203
lambda : self .fetch_whois_details .delay (
196
204
device_pk = self .device .pk ,
197
205
initial_ip_address = self .device ._initial_last_ip ,
198
206
new_ip_address = self .device .last_ip ,
199
207
)
200
208
)
209
+ elif whois_info_exists and self .is_whois_enabled :
210
+ self .manage_fuzzy_locations .delay (
211
+ self .device .pk , self .device .last_ip , add_existing = True
212
+ )
201
213
202
214
# device_pk is used when task fails to report for which device failure occurred
203
215
@shared_task (
@@ -259,6 +271,14 @@ def fetch_whois_details(self, device_pk, initial_ip_address, new_ip_address):
259
271
whois_obj .full_clean ()
260
272
whois_obj .save ()
261
273
logger .info (f"Successfully fetched WHOIS details for { new_ip_address } ." )
274
+ location_address = whois_obj .formatted_address
275
+ WhoIsService .manage_fuzzy_locations .delay (
276
+ device_pk ,
277
+ new_ip_address ,
278
+ data .location .latitude ,
279
+ data .location .longitude ,
280
+ location_address ,
281
+ )
262
282
263
283
# the following check ensures that for a case when device last_ip
264
284
# is not changed and there is no related WHOIS record, we do not
@@ -281,3 +301,71 @@ def delete_whois_record(ip_address):
281
301
queryset = WhoIsInfo .objects .filter (ip_address = ip_address )
282
302
if queryset .exists ():
283
303
queryset .delete ()
304
+
305
+ @shared_task
306
+ def manage_fuzzy_locations (
307
+ device_pk ,
308
+ ip_address ,
309
+ latitude = None ,
310
+ longitude = None ,
311
+ address = None ,
312
+ add_existing = False ,
313
+ ):
314
+ """
315
+ Creates/updates fuzzy location for a device based on the latitude and longitude
316
+ or attaches an existing location if `add_existing` is True.
317
+ Existing location here means a location of a device whose last_ip matches
318
+ the given ip_address.
319
+ """
320
+ Device = load_model ("config" , "Device" )
321
+ Location = load_model ("geo" , "Location" )
322
+ DeviceLocation = load_model ("geo" , "DeviceLocation" )
323
+
324
+ device_location = (
325
+ DeviceLocation .objects .filter (content_object_id = device_pk )
326
+ .select_related ("location" )
327
+ .first ()
328
+ )
329
+
330
+ if not device_location :
331
+ device_location = DeviceLocation (content_object_id = device_pk )
332
+
333
+ # if attaching an existing location, the current device should not have
334
+ # a location already set.
335
+ #TODO: Do we do this if device location exists but is marked fuzzy?
336
+ if add_existing and not device_location .location :
337
+ existing_device_with_location = (
338
+ Device .objects .select_related ("device_location" )
339
+ .filter (last_ip = ip_address , device_location__location__isnull = False )
340
+ .first ()
341
+ )
342
+ if existing_device_with_location :
343
+ location = existing_device_with_location .device_location .location
344
+ device_location .location = location
345
+ device_location .full_clean ()
346
+ device_location .save ()
347
+ elif latitude and longitude :
348
+ device = Device .objects .get (pk = device_pk )
349
+ coords = Point (longitude , latitude , srid = 4326 )
350
+ # Create/update the device location mapping, updating existing location
351
+ # if exists else create a new location
352
+ location_defaults = {
353
+ "name" : f"{ device .name } Location" ,
354
+ "type" : "outdoor" ,
355
+ "organization_id" : device .organization_id ,
356
+ "is_mobile" : False ,
357
+ "geometry" : coords ,
358
+ "address" : address ,
359
+ }
360
+ if device_location .location and device_location .location .fuzzy :
361
+ for attr , value in location_defaults .items ():
362
+ setattr (device_location .location , attr , value )
363
+ device_location .location .full_clean ()
364
+ device_location .location .save ()
365
+ elif not device_location .location :
366
+ location = Location (** location_defaults , fuzzy = True )
367
+ location .full_clean ()
368
+ location .save ()
369
+ device_location .location = location
370
+ device_location .full_clean ()
371
+ device_location .save ()
0 commit comments