add LocationRedirect and fallback slug for locations
This commit is contained in:
parent
40375585e1
commit
9d67cdcb75
4 changed files with 109 additions and 10 deletions
|
@ -2,6 +2,7 @@ import mimetypes
|
|||
from itertools import chain
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.decorators import detail_route, list_route
|
||||
from rest_framework.exceptions import NotFound, ValidationError
|
||||
|
@ -13,7 +14,7 @@ from c3nav.access.apply import filter_queryset_by_access
|
|||
from c3nav.mapdata.models import Building, Door, Hole, LocationGroup, Source, Space
|
||||
from c3nav.mapdata.models.geometry.section import SECTION_MODELS
|
||||
from c3nav.mapdata.models.geometry.space import SPACE_MODELS, Area, LineObstacle, Obstacle, Point, Stair
|
||||
from c3nav.mapdata.models.locations import LOCATION_MODELS, LocationSlug
|
||||
from c3nav.mapdata.models.locations import LOCATION_MODELS, Location, LocationRedirect, LocationSlug
|
||||
from c3nav.mapdata.models.section import Section
|
||||
from c3nav.mapdata.serializers.main import SourceSerializer
|
||||
from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin
|
||||
|
@ -137,11 +138,19 @@ class LocationGroupViewSet(MapdataViewSet):
|
|||
|
||||
|
||||
class LocationViewSet(RetrieveModelMixin, GenericViewSet):
|
||||
""" Add ?show_redirect=1 to suppress redirects and show them as JSON. """
|
||||
queryset = LocationSlug.objects.all()
|
||||
lookup_field = 'slug'
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return Response(self.get_object().get_child().serialize(include_type=True))
|
||||
def retrieve(self, request, slug=None, *args, **kwargs):
|
||||
result = Location.get_by_slug(slug, self.get_queryset())
|
||||
if result is None:
|
||||
raise NotFound
|
||||
if isinstance(result, LocationRedirect):
|
||||
if 'show_redirects' in request.GET:
|
||||
return Response(result.serialize(include_type=True))
|
||||
return redirect('../'+result.target.slug) # todo: why does redirect/reverse not work here?
|
||||
return Response(result.get_child().serialize(include_type=True))
|
||||
|
||||
@list_route(methods=['get'])
|
||||
def types(self, request):
|
||||
|
|
27
src/c3nav/mapdata/migrations/0002_locationredirect.py
Normal file
27
src/c3nav/mapdata/migrations/0002_locationredirect.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2017-05-11 19:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapdata', '0001_squashed_refactor_2017'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LocationRedirect',
|
||||
fields=[
|
||||
('locationslug_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='redirect', serialize=False, to='mapdata.LocationSlug')),
|
||||
('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='redirects', to='mapdata.LocationSlug', verbose_name='target')),
|
||||
],
|
||||
options={
|
||||
'default_related_name': 'redirect',
|
||||
},
|
||||
bases=('mapdata.locationslug',),
|
||||
),
|
||||
]
|
|
@ -5,9 +5,7 @@ from django.db import models
|
|||
EDITOR_FORM_MODELS = OrderedDict()
|
||||
|
||||
|
||||
class EditorFormMixin(models.Model):
|
||||
EditorForm = None
|
||||
|
||||
class SerializableMixin(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
@ -29,3 +27,10 @@ class EditorFormMixin(models.Model):
|
|||
result['type'] = self.__class__.__name__.lower()
|
||||
result['id'] = self.id
|
||||
return result
|
||||
|
||||
|
||||
class EditorFormMixin(SerializableMixin, models.Model):
|
||||
EditorForm = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import numpy as np
|
||||
from django.apps import apps
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -7,17 +8,25 @@ from django.utils.translation import get_language, ungettext_lazy
|
|||
|
||||
from c3nav.mapdata.fields import JSONField
|
||||
from c3nav.mapdata.lastupdate import get_last_mapdata_update
|
||||
from c3nav.mapdata.models.base import EditorFormMixin
|
||||
from c3nav.mapdata.models.base import EditorFormMixin, SerializableMixin
|
||||
|
||||
LOCATION_MODELS = []
|
||||
|
||||
|
||||
class LocationSlug(models.Model):
|
||||
class LocationSlug(SerializableMixin, models.Model):
|
||||
LOCATION_TYPE_CODES = {
|
||||
'Section': 'se',
|
||||
'Space': 'sp',
|
||||
'Area': 'a',
|
||||
'Point': 'p',
|
||||
'LocationGroup': 'g'
|
||||
}
|
||||
LOCATION_TYPE_BY_CODE = {code: model_name for model_name, code in LOCATION_TYPE_CODES.items()}
|
||||
slug = models.SlugField(_('name'), unique=True, null=True, max_length=50)
|
||||
|
||||
def get_child(self):
|
||||
# todo: cache this
|
||||
for model in LOCATION_MODELS:
|
||||
for model in LOCATION_MODELS+[LocationRedirect]:
|
||||
try:
|
||||
return getattr(self, model._meta.default_related_name)
|
||||
except AttributeError:
|
||||
|
@ -47,7 +56,7 @@ class Location(LocationSlug, EditorFormMixin, models.Model):
|
|||
|
||||
def _serialize(self, **kwargs):
|
||||
result = super()._serialize(**kwargs)
|
||||
result['slug'] = self.slug
|
||||
result['slug'] = self.get_slug()
|
||||
result['titles'] = self.titles
|
||||
result['can_search'] = self.can_search
|
||||
result['can_describe'] = self.can_search
|
||||
|
@ -55,6 +64,37 @@ class Location(LocationSlug, EditorFormMixin, models.Model):
|
|||
result['public'] = self.public
|
||||
return result
|
||||
|
||||
def get_slug(self):
|
||||
if self.slug is None:
|
||||
code = self.LOCATION_TYPE_CODES.get(self.__class__.__name__)
|
||||
if code is not None:
|
||||
return code+':'+str(self.id)
|
||||
return self.slug
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = LocationSlug.objects.all()
|
||||
|
||||
if ':' in slug:
|
||||
code, pk = slug.split(':', 1)
|
||||
model_name = cls.LOCATION_TYPE_BY_CODE.get(code)
|
||||
if model_name is None or not pk.isdigit():
|
||||
return None
|
||||
|
||||
model = apps.get_model('mapdata', model_name)
|
||||
try:
|
||||
location = model.objects.get(pk=pk)
|
||||
except model.DoesNotExist:
|
||||
return None
|
||||
|
||||
if location.slug is not None:
|
||||
return LocationRedirect(slug=slug, target=location)
|
||||
|
||||
return location
|
||||
|
||||
return queryset.filter(slug=slug).first()
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
if not hasattr(self, 'titles'):
|
||||
|
@ -135,6 +175,24 @@ class LocationGroup(Location, EditorFormMixin, models.Model):
|
|||
return result
|
||||
|
||||
|
||||
class LocationRedirect(LocationSlug):
|
||||
target = models.ForeignKey(LocationSlug, verbose_name=_('target'), related_name='redirects')
|
||||
|
||||
def _serialize(self, with_type=True, **kwargs):
|
||||
result = super()._serialize(with_type=with_type, **kwargs)
|
||||
if type(self.target) == LocationSlug:
|
||||
result['target'] = self.target.get_child().slug
|
||||
else:
|
||||
result['target'] = self.target.slug
|
||||
if with_type:
|
||||
result['type'] = 'redirect'
|
||||
result.pop('id')
|
||||
return result
|
||||
|
||||
class Meta:
|
||||
default_related_name = 'redirect'
|
||||
|
||||
|
||||
class PointLocation:
|
||||
def __init__(self, section: 'Section', x: int, y: int, request):
|
||||
self.section = section
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue