generate quests, but they can't be solved yet
This commit is contained in:
parent
38671a7947
commit
2805061c47
5 changed files with 135 additions and 2 deletions
|
@ -10,7 +10,7 @@ from typing import Sequence
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.forms import ChoiceField, Form, IntegerField, ModelForm, Select
|
from django.forms import ChoiceField, Form, IntegerField, ModelForm, Select, MultipleChoiceField
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import ngettext_lazy
|
from django.utils.translation import ngettext_lazy
|
||||||
|
@ -21,6 +21,7 @@ from c3nav.mapdata.forms import I18nModelFormMixin
|
||||||
from c3nav.mapdata.models import MapUpdate, Space
|
from c3nav.mapdata.models import MapUpdate, Space
|
||||||
from c3nav.mapdata.models.access import (AccessPermission, AccessPermissionToken, AccessPermissionTokenItem,
|
from c3nav.mapdata.models.access import (AccessPermission, AccessPermissionToken, AccessPermissionTokenItem,
|
||||||
AccessRestriction, AccessRestrictionGroup)
|
AccessRestriction, AccessRestrictionGroup)
|
||||||
|
from c3nav.mapdata.quests import quest_types
|
||||||
from c3nav.site.models import Announcement
|
from c3nav.site.models import Announcement
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,10 +29,19 @@ class UserPermissionsForm(ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['review_group_reports'].label_from_instance = lambda obj: obj.title
|
self.fields['review_group_reports'].label_from_instance = lambda obj: obj.title
|
||||||
|
self.fields['allowed_quests'] = MultipleChoiceField(
|
||||||
|
label=_('Available quests'),
|
||||||
|
choices=[(key, quest.quest_type_label) for key, quest in quest_types.items()],
|
||||||
|
initial=self.instance.quests,
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.instance.quests = self.cleaned_data['allowed_quests']
|
||||||
|
super().save()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserPermissions
|
model = UserPermissions
|
||||||
exclude = ('user', 'max_changeset_changes', 'api_secret')
|
exclude = ('user', 'max_changeset_changes', 'api_secret', 'quests')
|
||||||
|
|
||||||
|
|
||||||
class AccessPermissionForm(Form):
|
class AccessPermissionForm(Form):
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.core.cache import cache
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.utils.functional import cached_property, lazy
|
from django.utils.functional import cached_property, lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_pydantic_field.fields import SchemaField
|
||||||
|
|
||||||
from c3nav.mapdata.models import Space
|
from c3nav.mapdata.models import Space
|
||||||
from c3nav.mapdata.models.access import AccessPermission
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
|
@ -41,6 +42,7 @@ class UserPermissions(models.Model):
|
||||||
mesh_control = models.BooleanField(default=False, verbose_name=_('can access mesh control'))
|
mesh_control = models.BooleanField(default=False, verbose_name=_('can access mesh control'))
|
||||||
|
|
||||||
nonpublic_themes = models.BooleanField(default=False, verbose_name=_('show non-public themes in theme selector'))
|
nonpublic_themes = models.BooleanField(default=False, verbose_name=_('show non-public themes in theme selector'))
|
||||||
|
quests: list[str] = SchemaField(schema=list[str], default=list)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('User Permissions')
|
verbose_name = _('User Permissions')
|
||||||
|
|
|
@ -21,6 +21,7 @@ from c3nav.mapdata.grid import grid
|
||||||
from c3nav.mapdata.models import Source, Theme, Area, Space
|
from c3nav.mapdata.models import Source, Theme, Area, Space
|
||||||
from c3nav.mapdata.models.geometry.space import ObstacleGroup, Obstacle
|
from c3nav.mapdata.models.geometry.space import ObstacleGroup, Obstacle
|
||||||
from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Position, LocationGroup
|
from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Position, LocationGroup
|
||||||
|
from c3nav.mapdata.quests import QuestSchema, get_all_quests_for_request
|
||||||
from c3nav.mapdata.render.theme import ColorManager
|
from c3nav.mapdata.render.theme import ColorManager
|
||||||
from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter
|
from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter
|
||||||
from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID
|
from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID
|
||||||
|
@ -391,3 +392,14 @@ def legend_for_theme(request, theme_id: int):
|
||||||
for group in obstaclegroups)
|
for group in obstaclegroups)
|
||||||
if item.fill or item.border],
|
if item.fill or item.border],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Quests
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@map_api_router.get('/quests/', summary="get open quests",
|
||||||
|
response={200: list[QuestSchema], **auth_responses})
|
||||||
|
def list_quests(request):
|
||||||
|
return get_all_quests_for_request(request)
|
|
@ -493,6 +493,8 @@ class RangingBeacon(SpaceGeometryMixin, models.Model):
|
||||||
validators=[MinValueValidator(Decimal('0'))])
|
validators=[MinValueValidator(Decimal('0'))])
|
||||||
comment = models.TextField(null=True, blank=True, verbose_name=_('comment'))
|
comment = models.TextField(null=True, blank=True, verbose_name=_('comment'))
|
||||||
|
|
||||||
|
altitude_quest = models.BooleanField(_('altitude quest'), default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Ranging beacon')
|
verbose_name = _('Ranging beacon')
|
||||||
verbose_name_plural = _('Ranging beacons')
|
verbose_name_plural = _('Ranging beacons')
|
||||||
|
|
107
src/c3nav/mapdata/quests.py
Normal file
107
src/c3nav/mapdata/quests.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
from abc import abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from itertools import chain
|
||||||
|
from typing import Self, Optional, Any, Type
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from pydantic.type_adapter import TypeAdapter
|
||||||
|
from shapely.geometry import Point, mapping
|
||||||
|
|
||||||
|
from c3nav.api.schema import BaseSchema, PointSchema
|
||||||
|
from c3nav.mapdata.models.access import AccessPermission
|
||||||
|
from c3nav.mapdata.models.geometry.space import RangingBeacon
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Quest:
|
||||||
|
obj: Any
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def point(self) -> dict:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def level_id(self) -> int:
|
||||||
|
return self.obj.level_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return str(self.obj.pk)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _qs_for_request(cls, request):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _obj_to_quests(cls, obj) -> list[Self]:
|
||||||
|
return [cls(obj=obj)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_for_request(cls, request, identifier: Any) -> Optional[Self]:
|
||||||
|
if not identifier.isdigit():
|
||||||
|
return None
|
||||||
|
results = list(chain(
|
||||||
|
+(cls._obj_to_quests(obj) for obj in cls._qs_for_request(request).filter(pk=int(identifier)))
|
||||||
|
))
|
||||||
|
if len(results) > 1:
|
||||||
|
raise ValueError('wrong number of results')
|
||||||
|
return results[0] if results else None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_for_request(cls, request) -> list[Self]:
|
||||||
|
return list(chain(
|
||||||
|
*(cls._obj_to_quests(obj) for obj in cls._qs_for_request(request))
|
||||||
|
))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cached_get_all_for_request(cls, request) -> list["QuestSchema"]:
|
||||||
|
cache_key = f'quests:{cls.identifier}:{AccessPermission.cache_key_for_request(request)}'
|
||||||
|
result = cache.get(cache_key, None)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
adapter = TypeAdapter(list[QuestSchema])
|
||||||
|
result = adapter.dump_python(adapter.validate_python(cls.get_all_for_request(request)))
|
||||||
|
cache.set(cache_key, result, 900)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
quest_types: dict[str, Type[BaseModel]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_quest(cls):
|
||||||
|
quest_types[cls.quest_type] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
@register_quest
|
||||||
|
@dataclass
|
||||||
|
class RangingBeaconAltitudeQuest(Quest):
|
||||||
|
quest_type = "ranging_beacon_altitude"
|
||||||
|
quest_type_label = _('Ranging Beacon Altitude')
|
||||||
|
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').filter(altitude_quest=True)[:10]
|
||||||
|
|
||||||
|
|
||||||
|
class QuestSchema(BaseSchema):
|
||||||
|
quest_type: str
|
||||||
|
identifier: str
|
||||||
|
level_id: int
|
||||||
|
point: PointSchema
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_quests_for_request(request) -> list[QuestSchema]:
|
||||||
|
return list(chain(*(
|
||||||
|
quest.cached_get_all_for_request(request)
|
||||||
|
for key, quest in quest_types.items()
|
||||||
|
if request.user.is_superuser or key in request.user_permissions.quests
|
||||||
|
)))
|
Loading…
Add table
Add a link
Reference in a new issue