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 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):
|
||||||
|
|
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()
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue