From b9e43fdb585e6915e9140a19677b6c5b666fd681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Wed, 25 Dec 2024 21:23:35 +0100 Subject: [PATCH] add leavedescription quest --- .../editor/templates/editor/quest_form.html | 8 +- src/c3nav/editor/views/quest.py | 1 + src/c3nav/mapdata/quests/base.py | 8 +- src/c3nav/mapdata/quests/simple.py | 101 +++++++++++++++++- 4 files changed, 110 insertions(+), 8 deletions(-) diff --git a/src/c3nav/editor/templates/editor/quest_form.html b/src/c3nav/editor/templates/editor/quest_form.html index 414aa1de..597358b5 100644 --- a/src/c3nav/editor/templates/editor/quest_form.html +++ b/src/c3nav/editor/templates/editor/quest_form.html @@ -5,11 +5,9 @@

{{ title }}

- {% if back_url %} -

- « {% trans 'back' %} -

- {% endif %} + {% for line in description %} +

{{ line }}

+ {% endfor %}
{% csrf_token %} diff --git a/src/c3nav/editor/views/quest.py b/src/c3nav/editor/views/quest.py index e1fbca2d..24e37be3 100644 --- a/src/c3nav/editor/views/quest.py +++ b/src/c3nav/editor/views/quest.py @@ -34,6 +34,7 @@ class QuestFormView(FormView): return { **super().get_context_data(**kwargs), "title": self.quest.quest_type_label, + "description": self.quest.quest_description, } def form_valid(self, form): diff --git a/src/c3nav/mapdata/quests/base.py b/src/c3nav/mapdata/quests/base.py index 5a292241..399b7169 100644 --- a/src/c3nav/mapdata/quests/base.py +++ b/src/c3nav/mapdata/quests/base.py @@ -17,6 +17,10 @@ from c3nav.mapdata.models.access import AccessPermission class Quest: obj: Any + @property + def quest_description(self) -> list[str]: + return [] + @property def point(self) -> dict: raise NotImplementedError @@ -38,7 +42,7 @@ class Quest: return [cls(obj=obj)] @classmethod - def get_for_request(cls, request, identifier: Any) -> Optional[Self]: + def get_for_request(cls, request, identifier: str) -> Optional[Self]: if not identifier.isdigit(): return None if not (request.user.is_superuser or cls.quest_type in request.user_permissions.quests): @@ -61,7 +65,7 @@ class Quest: @classmethod def cached_get_all_for_request(cls, request) -> list["QuestSchema"]: - cache_key = f'quests:{cls.identifier}:{AccessPermission.cache_key_for_request(request)}' + cache_key = f'quests:{cls.quest_type}:{AccessPermission.cache_key_for_request(request)}' result = cache.get(cache_key, None) if result is not None: return result diff --git a/src/c3nav/mapdata/quests/simple.py b/src/c3nav/mapdata/quests/simple.py index e738d0d8..e9940a68 100644 --- a/src/c3nav/mapdata/quests/simple.py +++ b/src/c3nav/mapdata/quests/simple.py @@ -1,12 +1,18 @@ from dataclasses import dataclass +from operator import attrgetter +from typing import ClassVar from django.core.exceptions import ValidationError +from django.db.models import F from django.utils.translation import gettext_lazy as _ from shapely import Point from shapely.geometry import mapping -from c3nav.mapdata.models.geometry.space import RangingBeacon +from c3nav.mapdata.forms import I18nModelFormMixin +from c3nav.mapdata.models import GraphEdge, Space +from c3nav.mapdata.models.geometry.space import RangingBeacon, LeaveDescription from c3nav.mapdata.quests.base import register_quest, Quest, ChangeSetModelForm +from c3nav.mapdata.utils.geometry import unwrap_geom class RangingBeaconAltitudeQuestForm(ChangeSetModelForm): @@ -51,3 +57,96 @@ class RangingBeaconAltitudeQuest(Quest): def _qs_for_request(cls, request): return RangingBeacon.qs_for_request(request).select_related('space', 'space__level').filter(altitude_quest=True) + + +class LeaveDescriptionQuestForm(I18nModelFormMixin, ChangeSetModelForm): + class Meta: + model = LeaveDescription + fields = ("description", ) + + @property + def changeset_title(self): + return f'LeaveDesscription Quest: {self.instance.space.title} → {self.instance.target_space.title}' + + +@register_quest +@dataclass +class LeaveDescriptionQuest(Quest): + quest_type = "leave_description" + quest_type_label = _('Leave Description') + form_class = LeaveDescriptionQuestForm + obj: ClassVar + space: Space + target_space: Space + the_point: Point + + @property + def quest_description(self) -> list[str]: + return [ + _("Please provide a description to be used when leaving “%(from_space)s” towards “%(to_space)s”.") % { + "from_space": self.space.title, + "to_space": self.target_space.title, + }, + _("This will be used all doors that lead from this space to the other, not just the highlighted one! " + "So please be generic if there is more then one."), + ] + + @property + def point(self) -> dict: + return mapping(self.the_point) + + @property + def level_id(self) -> int: + if self.space.level_id == self.target_space.level_id: + return self.space.level_id + levels = sorted((self.space.level, self.target_space.level), key=attrgetter("base_altitude")) + if self.space.level.on_top_of_id is None and self.target_space.level.on_top_of_id is None: + return levels[1].pk + return levels[0].pk + + @property + def identifier(self) -> str: + return f"{self.space.pk}-{self.target_space.pk}" + + @classmethod + def get_all_for_request(cls, request, space_ids: tuple[int, int] = ()): + qs = Space.qs_for_request(request) + if space_ids: + qs = qs.filter(pk__in=space_ids) + spaces = {space.pk: space for space in qs.select_related("level")} + existing = set(tuple(item) for item in LeaveDescription.objects.values_list("space_id", "target_space_id")) + more_filter = {} if not space_ids else {"from_node__space_id": space_ids[0], "to_node__space_id": space_ids[1]} + edges = { + (from_space, to_space): (from_point, to_point) + for from_space, to_space, from_point, to_point in GraphEdge.objects.filter( + from_node__space__in=spaces, + to_node__space__in=spaces, + **more_filter, + ).exclude( + from_node__space=F("to_node__space") + ).values_list("from_node__space_id", "to_node__space_id", "from_node__geometry", "to_node__geometry") + if (from_space, to_space) not in existing + } + return [ + cls( + space=spaces[from_space], + target_space=spaces[to_space], + the_point=unwrap_geom(from_point), + ) + for (from_space, to_space), (from_point, to_point) in edges.items() + ] + + @classmethod + def get_for_request(cls, request, identifier: str): + space_ids = identifier.split('-') + if len(space_ids) != 2 or not (space_ids[0].isdigit() and space_ids[1].isdigit()): + return None + + results = cls.get_all_for_request(request, space_ids=tuple(int(i) for i in space_ids)) + return results[0] if results else None + + def get_form_kwargs(self, request): + instance = LeaveDescription() + instance.space = self.space + instance.target_space = self.target_space + return {"instance": instance}