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.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.translation import gettext_lazy as _
|
||||
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.access import (AccessPermission, AccessPermissionToken, AccessPermissionTokenItem,
|
||||
AccessRestriction, AccessRestrictionGroup)
|
||||
from c3nav.mapdata.quests import quest_types
|
||||
from c3nav.site.models import Announcement
|
||||
|
||||
|
||||
|
@ -28,10 +29,19 @@ class UserPermissionsForm(ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
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:
|
||||
model = UserPermissions
|
||||
exclude = ('user', 'max_changeset_changes', 'api_secret')
|
||||
exclude = ('user', 'max_changeset_changes', 'api_secret', 'quests')
|
||||
|
||||
|
||||
class AccessPermissionForm(Form):
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.core.cache import cache
|
|||
from django.db import models, transaction
|
||||
from django.utils.functional import cached_property, lazy
|
||||
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.access import AccessPermission
|
||||
|
@ -41,6 +42,7 @@ class UserPermissions(models.Model):
|
|||
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'))
|
||||
quests: list[str] = SchemaField(schema=list[str], default=list)
|
||||
|
||||
class Meta:
|
||||
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.geometry.space import ObstacleGroup, Obstacle
|
||||
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.schemas.filters import BySearchableFilter, RemoveGeometryFilter
|
||||
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)
|
||||
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'))])
|
||||
comment = models.TextField(null=True, blank=True, verbose_name=_('comment'))
|
||||
|
||||
altitude_quest = models.BooleanField(_('altitude quest'), default=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Ranging beacon')
|
||||
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