add LocationRedirect and fallback slug for locations

This commit is contained in:
Laura Klünder 2017-05-11 22:40:48 +02:00
parent 40375585e1
commit 9d67cdcb75
4 changed files with 109 additions and 10 deletions

View file

@ -2,6 +2,7 @@ import mimetypes
from itertools import chain from itertools import chain
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
from rest_framework.exceptions import NotFound, ValidationError 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 import Building, Door, Hole, LocationGroup, Source, Space
from c3nav.mapdata.models.geometry.section import SECTION_MODELS 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.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.models.section import Section
from c3nav.mapdata.serializers.main import SourceSerializer from c3nav.mapdata.serializers.main import SourceSerializer
from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin from c3nav.mapdata.utils.cache import CachedReadOnlyViewSetMixin
@ -137,11 +138,19 @@ class LocationGroupViewSet(MapdataViewSet):
class LocationViewSet(RetrieveModelMixin, GenericViewSet): class LocationViewSet(RetrieveModelMixin, GenericViewSet):
""" Add ?show_redirect=1 to suppress redirects and show them as JSON. """
queryset = LocationSlug.objects.all() queryset = LocationSlug.objects.all()
lookup_field = 'slug' lookup_field = 'slug'
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, slug=None, *args, **kwargs):
return Response(self.get_object().get_child().serialize(include_type=True)) 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']) @list_route(methods=['get'])
def types(self, request): def types(self, request):

View 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',),
),
]

View file

@ -5,9 +5,7 @@ from django.db import models
EDITOR_FORM_MODELS = OrderedDict() EDITOR_FORM_MODELS = OrderedDict()
class EditorFormMixin(models.Model): class SerializableMixin(models.Model):
EditorForm = None
class Meta: class Meta:
abstract = True abstract = True
@ -29,3 +27,10 @@ class EditorFormMixin(models.Model):
result['type'] = self.__class__.__name__.lower() result['type'] = self.__class__.__name__.lower()
result['id'] = self.id result['id'] = self.id
return result return result
class EditorFormMixin(SerializableMixin, models.Model):
EditorForm = None
class Meta:
abstract = True

View file

@ -1,4 +1,5 @@
import numpy as np import numpy as np
from django.apps import apps
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.functional import cached_property 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.fields import JSONField
from c3nav.mapdata.lastupdate import get_last_mapdata_update 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 = [] 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) slug = models.SlugField(_('name'), unique=True, null=True, max_length=50)
def get_child(self): def get_child(self):
# todo: cache this # todo: cache this
for model in LOCATION_MODELS: for model in LOCATION_MODELS+[LocationRedirect]:
try: try:
return getattr(self, model._meta.default_related_name) return getattr(self, model._meta.default_related_name)
except AttributeError: except AttributeError:
@ -47,7 +56,7 @@ class Location(LocationSlug, EditorFormMixin, models.Model):
def _serialize(self, **kwargs): def _serialize(self, **kwargs):
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
result['slug'] = self.slug result['slug'] = self.get_slug()
result['titles'] = self.titles result['titles'] = self.titles
result['can_search'] = self.can_search result['can_search'] = self.can_search
result['can_describe'] = self.can_search result['can_describe'] = self.can_search
@ -55,6 +64,37 @@ class Location(LocationSlug, EditorFormMixin, models.Model):
result['public'] = self.public result['public'] = self.public
return result 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 @property
def title(self): def title(self):
if not hasattr(self, 'titles'): if not hasattr(self, 'titles'):
@ -135,6 +175,24 @@ class LocationGroup(Location, EditorFormMixin, models.Model):
return result 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: class PointLocation:
def __init__(self, section: 'Section', x: int, y: int, request): def __init__(self, section: 'Section', x: int, y: int, request):
self.section = section self.section = section