2024-12-26 03:44:05 +01:00
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
from django.core.exceptions import ValidationError
|
2024-12-27 01:14:52 +01:00
|
|
|
from django.forms import CharField, HiddenInput
|
2024-12-26 03:44:05 +01:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from shapely import Point
|
|
|
|
from shapely.geometry import mapping
|
|
|
|
|
2024-12-27 19:21:39 +01:00
|
|
|
from c3nav.mapdata.models.geometry.space import RangingBeacon, BeaconMeasurement
|
2024-12-26 03:44:05 +01:00
|
|
|
from c3nav.mapdata.quests.base import ChangeSetModelForm, register_quest, Quest
|
2024-12-27 19:21:39 +01:00
|
|
|
from c3nav.routing.schemas import BeaconMeasurementDataSchema
|
2024-12-26 03:44:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
class RangingBeaconAltitudeQuestForm(ChangeSetModelForm):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.fields["altitude"].label = (
|
|
|
|
_('How many meters above ground is the access point “%s” mounted?') % self.instance.comment
|
|
|
|
)
|
|
|
|
|
|
|
|
def clean_altitude(self):
|
|
|
|
data = self.cleaned_data["altitude"]
|
|
|
|
if not data:
|
|
|
|
raise ValidationError(_("The AP should not be 0m above ground."))
|
|
|
|
return data
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = RangingBeacon
|
|
|
|
fields = ("altitude", )
|
|
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
self.instance.altitude_quest = False
|
|
|
|
return super().save(*args, **kwargs)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def changeset_title(self):
|
|
|
|
return f'Altitude Quest: {self.instance.title}'
|
|
|
|
|
|
|
|
|
|
|
|
@register_quest
|
|
|
|
@dataclass
|
|
|
|
class RangingBeaconAltitudeQuest(Quest):
|
|
|
|
quest_type = "ranging_beacon_altitude"
|
|
|
|
quest_type_label = _('Ranging Beacon Altitude')
|
|
|
|
quest_type_icon = "router"
|
|
|
|
form_class = RangingBeaconAltitudeQuestForm
|
|
|
|
obj: RangingBeacon
|
|
|
|
|
|
|
|
@property
|
|
|
|
def point(self) -> Point:
|
|
|
|
return mapping(self.obj.geometry)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _qs_for_request(cls, request):
|
|
|
|
return RangingBeacon.qs_for_request(request).select_related('space',
|
|
|
|
'space__level').filter(altitude_quest=True)
|
2024-12-26 22:43:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
class RangingBeaconBSSIDsQuestForm(ChangeSetModelForm):
|
2024-12-27 01:14:52 +01:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.fields["look_for_ap"] = CharField(disabled=True, initial=self.instance.import_tag[4:],
|
|
|
|
widget=HiddenInput())
|
2024-12-28 15:21:53 +01:00
|
|
|
self.fields["addresses"].widget = HiddenInput()
|
2024-12-27 01:14:52 +01:00
|
|
|
|
2024-12-28 15:21:53 +01:00
|
|
|
def clean_addresses(self):
|
|
|
|
data = self.cleaned_data["addresses"]
|
2024-12-26 22:43:19 +01:00
|
|
|
if not data:
|
|
|
|
raise ValidationError(_("Need at least one bssid."))
|
|
|
|
return data
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = RangingBeacon
|
2024-12-28 15:21:53 +01:00
|
|
|
fields = ("addresses", )
|
2024-12-26 22:43:19 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def changeset_title(self):
|
|
|
|
return f'Ranging Beacon BSSID Quest: {self.instance.title}'
|
|
|
|
|
|
|
|
|
|
|
|
@register_quest
|
|
|
|
@dataclass
|
|
|
|
class RangingBeaconBSSIDsQuest(Quest):
|
|
|
|
quest_type = "ranging_beacon_bssids"
|
|
|
|
quest_type_label = _('Ranging Beacon Identifier')
|
|
|
|
quest_type_icon = "wifi_find"
|
|
|
|
form_class = RangingBeaconBSSIDsQuestForm
|
|
|
|
obj: RangingBeacon
|
|
|
|
|
|
|
|
@property
|
|
|
|
def quest_description(self) -> list[str]:
|
|
|
|
return [
|
2024-12-27 01:14:52 +01:00
|
|
|
_("This quest only works in the app. It works fully automatically."),
|
2024-12-27 22:05:19 +01:00
|
|
|
_("We are trying to find the BSSIDs broadcast by “%s”.") % self.obj.ap_name,
|
|
|
|
_("Please stand near “%s” and wait for the submit button to appear.") % self.obj.ap_name,
|
2024-12-27 14:45:01 +01:00
|
|
|
_("Do not close this popup until then."),
|
2024-12-26 22:43:19 +01:00
|
|
|
_("This should happen within less than a minute."),
|
|
|
|
]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def point(self) -> Point:
|
|
|
|
return mapping(self.obj.geometry)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _qs_for_request(cls, request):
|
2024-12-28 15:21:53 +01:00
|
|
|
return RangingBeacon.qs_for_request(request).filter(ap_name__isnull=False, addresses=[])
|
2024-12-27 19:21:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
class BeaconMeasurementQuestForm(ChangeSetModelForm):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.fields["data"].widget = HiddenInput()
|
|
|
|
|
|
|
|
def clean_bssids(self):
|
|
|
|
data = self.cleaned_data["data"]
|
|
|
|
if not data:
|
|
|
|
raise ValidationError(_("Need at least one scan."))
|
|
|
|
return data
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = BeaconMeasurement
|
|
|
|
fields = ("data", )
|
|
|
|
|
|
|
|
@property
|
|
|
|
def changeset_title(self):
|
|
|
|
return f'Beacon Measurement Quest: {self.instance.title}'
|
|
|
|
|
|
|
|
|
|
|
|
@register_quest
|
|
|
|
@dataclass
|
|
|
|
class BeaconMeasurementQuest(Quest):
|
|
|
|
quest_type = "beacon_measurement"
|
|
|
|
quest_type_label = _('Wifi/BLE Positioning')
|
|
|
|
quest_type_icon = "wifi"
|
|
|
|
form_class = BeaconMeasurementQuestForm
|
|
|
|
obj: BeaconMeasurement
|
|
|
|
|
|
|
|
@property
|
|
|
|
def quest_description(self) -> list[str]:
|
|
|
|
return [
|
|
|
|
_("Please stand as close to the given location as possible. "
|
|
|
|
"Feel free to close this window again to double-check."),
|
|
|
|
_("When you're ready, please click the button below and wait for measurements to arrive."),
|
|
|
|
]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def point(self) -> Point:
|
|
|
|
return mapping(self.obj.geometry)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _qs_for_request(cls, request):
|
|
|
|
return BeaconMeasurement.qs_for_request(request).filter(data=BeaconMeasurementDataSchema())
|