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 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):

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()
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

View file

@ -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