rename Section back to Level

This commit is contained in:
Laura Klünder 2017-06-11 14:43:14 +02:00
parent 27ff35e785
commit 1498b7aeb0
27 changed files with 416 additions and 334 deletions

View file

@ -8,12 +8,12 @@ from rest_framework.response import Response
from rest_framework.routers import SimpleRouter from rest_framework.routers import SimpleRouter
from c3nav.editor.api import EditorViewSet from c3nav.editor.api import EditorViewSet
from c3nav.mapdata.api import (AreaViewSet, BuildingViewSet, ColumnViewSet, DoorViewSet, HoleViewSet, from c3nav.mapdata.api import (AreaViewSet, BuildingViewSet, ColumnViewSet, DoorViewSet, HoleViewSet, LevelViewSet,
LineObstacleViewSet, LocationGroupViewSet, LocationViewSet, ObstacleViewSet, LineObstacleViewSet, LocationGroupViewSet, LocationViewSet, ObstacleViewSet,
PointViewSet, SectionViewSet, SourceViewSet, SpaceViewSet, StairViewSet) PointViewSet, SourceViewSet, SpaceViewSet, StairViewSet)
router = SimpleRouter() router = SimpleRouter()
router.register(r'sections', SectionViewSet) router.register(r'levels', LevelViewSet)
router.register(r'buildings', BuildingViewSet) router.register(r'buildings', BuildingViewSet)
router.register(r'spaces', SpaceViewSet) router.register(r'spaces', SpaceViewSet)
router.register(r'doors', DoorViewSet) router.register(r'doors', DoorViewSet)

View file

@ -7,19 +7,19 @@ from rest_framework.response import Response
from rest_framework.viewsets import ViewSet from rest_framework.viewsets import ViewSet
from shapely.ops import cascaded_union from shapely.ops import cascaded_union
from c3nav.mapdata.models import Section, Space from c3nav.mapdata.models import Level, Space
class EditorViewSet(ViewSet): class EditorViewSet(ViewSet):
""" """
Editor API Editor API
/geometries/ returns a list of geojson features, you have to specify ?section=<id> or ?space=<id> /geometries/ returns a list of geojson features, you have to specify ?level=<id> or ?space=<id>
/geometrystyles/ returns styling information for all geometry types /geometrystyles/ returns styling information for all geometry types
""" """
def _get_section_geometries(self, section: Section): def _get_level_geometries(self, level: Level):
buildings = section.buildings.all() buildings = level.buildings.all()
buildings_geom = cascaded_union([building.geometry for building in buildings]) buildings_geom = cascaded_union([building.geometry for building in buildings])
spaces = {space.id: space for space in section.spaces.all()} spaces = {space.id: space for space in level.spaces.all()}
holes_geom = [] holes_geom = []
for space in spaces.values(): for space in spaces.values():
if space.outside: if space.outside:
@ -38,66 +38,66 @@ class EditorViewSet(ViewSet):
results = [] results = []
results.extend(buildings) results.extend(buildings)
for door in section.doors.all(): for door in level.doors.all():
results.append(door) results.append(door)
results.extend(spaces.values()) results.extend(spaces.values())
return results return results
def _get_sections_pk(self, section): def _get_levels_pk(self, level):
sections_under = () levels_under = ()
sections_on_top = () levels_on_top = ()
lower_section = section.lower().first() lower_level = level.lower().first()
primary_sections = (section,) + ((lower_section,) if lower_section else ()) primary_levels = (level,) + ((lower_level,) if lower_level else ())
secondary_sections = Section.objects.filter(on_top_of__in=primary_sections).values_list('pk', 'on_top_of') secondary_levels = Level.objects.filter(on_top_of__in=primary_levels).values_list('pk', 'on_top_of')
if lower_section: if lower_level:
sections_under = tuple(pk for pk, on_top_of in secondary_sections if on_top_of == lower_section.pk) levels_under = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == lower_level.pk)
if True: if True:
sections_on_top = tuple(pk for pk, on_top_of in secondary_sections if on_top_of == section.pk) levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk)
sections = chain([section.pk], sections_under, sections_on_top) levels = chain([level.pk], levels_under, levels_on_top)
return sections, sections_on_top, sections_under return levels, levels_on_top, levels_under
@list_route(methods=['get']) @list_route(methods=['get'])
def geometries(self, request, *args, **kwargs): def geometries(self, request, *args, **kwargs):
section = request.GET.get('section') level = request.GET.get('level')
space = request.GET.get('space') space = request.GET.get('space')
if section is not None: if level is not None:
if space is not None: if space is not None:
raise ValidationError('Only section or space can be specified.') raise ValidationError('Only level or space can be specified.')
section = get_object_or_404(Section, pk=section) level = get_object_or_404(Level, pk=level)
sections, sections_on_top, sections_under = self._get_sections_pk(section) levels, levels_on_top, levels_under = self._get_levels_pk(level)
sections = Section.objects.filter(pk__in=sections).prefetch_related('buildings', 'spaces', 'doors', levels = Level.objects.filter(pk__in=levels).prefetch_related('buildings', 'spaces', 'doors',
'spaces__groups', 'spaces__holes', 'spaces__groups', 'spaces__holes',
'spaces__columns') 'spaces__columns')
sections = {s.pk: s for s in sections} levels = {s.pk: s for s in levels}
section = sections[section.pk] level = levels[level.pk]
sections_under = [sections[pk] for pk in sections_under] levels_under = [levels[pk] for pk in levels_under]
sections_on_top = [sections[pk] for pk in sections_on_top] levels_on_top = [levels[pk] for pk in levels_on_top]
results = chain( results = chain(
*(self._get_section_geometries(s) for s in sections_under), *(self._get_level_geometries(s) for s in levels_under),
self._get_section_geometries(section), self._get_level_geometries(level),
*(self._get_section_geometries(s) for s in sections_on_top) *(self._get_level_geometries(s) for s in levels_on_top)
) )
return Response([obj.to_geojson() for obj in results]) return Response([obj.to_geojson() for obj in results])
elif space is not None: elif space is not None:
space = get_object_or_404(Space.objects.select_related('section', 'section__on_top_of'), pk=space) space = get_object_or_404(Space.objects.select_related('level', 'level__on_top_of'), pk=space)
section = space.section level = space.level
doors = [door for door in section.doors.all() if door.geometry.intersects(space.geometry)] doors = [door for door in level.doors.all() if door.geometry.intersects(space.geometry)]
doors_space_geom = cascaded_union([door.geometry for door in doors]+[space.geometry]) doors_space_geom = cascaded_union([door.geometry for door in doors]+[space.geometry])
sections, sections_on_top, sections_under = self._get_sections_pk(section.primary_section) levels, levels_on_top, levels_under = self._get_levels_pk(level.primary_level)
other_spaces = Space.objects.filter(section__pk__in=sections).prefetch_related('groups') other_spaces = Space.objects.filter(level__pk__in=levels).prefetch_related('groups')
other_spaces = [s for s in other_spaces other_spaces = [s for s in other_spaces
if s.geometry.intersects(doors_space_geom) and s.pk != space.pk] if s.geometry.intersects(doors_space_geom) and s.pk != space.pk]
space.bounds = True space.bounds = True
buildings = section.buildings.all() buildings = level.buildings.all()
buildings_geom = cascaded_union([building.geometry for building in buildings]) buildings_geom = cascaded_union([building.geometry for building in buildings])
for other_space in other_spaces: for other_space in other_spaces:
if other_space.outside: if other_space.outside:
@ -122,7 +122,7 @@ class EditorViewSet(ViewSet):
) )
return Response(sum([self._get_geojsons(obj) for obj in results], ())) return Response(sum([self._get_geojsons(obj) for obj in results], ()))
else: else:
raise ValidationError('No section or space specified.') raise ValidationError('No level or space specified.')
def _get_geojsons(self, obj): def _get_geojsons(self, obj):
return ((obj.to_shadow_geojson(),) if hasattr(obj, 'to_shadow_geojson') else ()) + (obj.to_geojson(),) return ((obj.to_shadow_geojson(),) if hasattr(obj, 'to_shadow_geojson') else ()) + (obj.to_geojson(),)

View file

@ -16,9 +16,9 @@ class MapitemFormMixin(ModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
creating = not self.instance.pk creating = not self.instance.pk
if 'section' in self.fields: if 'level' in self.fields:
# hide section widget # hide level widget
self.fields['section'].widget = HiddenInput() self.fields['level'].widget = HiddenInput()
if 'space' in self.fields: if 'space' in self.fields:
# hide space widget # hide space widget

View file

@ -110,7 +110,7 @@ legend {
right: 8px; right: 8px;
top: 8px; top: 8px;
} }
[data-sections], [data-subsections] { [data-levels], [data-sublevels] {
display:none; display:none;
} }
form button.invisiblesubmit { form button.invisiblesubmit {
@ -211,47 +211,47 @@ form button.invisiblesubmit {
} }
/* leaftlet sections control */ /* leaftlet levels control */
.leaflet-control-sections { .leaflet-control-levels {
overflow:hidden; overflow:hidden;
} }
.leaflet-control-subsections { .leaflet-control-sublevels {
position:absolute; position:absolute;
} }
.leaflet-control-sections a, .leaflet-control-sections a:hover { .leaflet-control-levels a, .leaflet-control-levels a:hover {
width: auto; width: auto;
font-size: 14px; font-size: 14px;
padding: 0 7px; padding: 0 7px;
white-space: nowrap; white-space: nowrap;
} }
.leaflet-touch .leaflet-control-sections a, .leaflet-touch .leaflet-control-sections a:hover { .leaflet-touch .leaflet-control-levels a, .leaflet-touch .leaflet-control-levels a:hover {
width: auto; width: auto;
height:36px; height:36px;
line-height:37px; line-height:37px;
font-size: 14px; font-size: 14px;
padding: 0 12px; padding: 0 12px;
} }
.leaflet-control-sections a, .leaflet-control-sections a:hover { .leaflet-control-levels a, .leaflet-control-levels a:hover {
opacity:0; opacity:0;
margin-top:-26px; margin-top:-26px;
} }
.leaflet-touch .leaflet-control-sections a, .leaflet-touch .leaflet-control-sections a:hover { .leaflet-touch .leaflet-control-levels a, .leaflet-touch .leaflet-control-levels a:hover {
margin-top:-36px; margin-top:-36px;
} }
.leaflet-control-sections a.current, .leaflet-control-sections-expanded a, .leaflet-control-sections-expanded a:hover, .leaflet-control-levels a.current, .leaflet-control-levels-expanded a, .leaflet-control-levels-expanded a:hover,
.leaflet-touch .leaflet-control-sections a.current, .leaflet-touch .leaflet-control-sections-expanded a, .leaflet-touch .leaflet-control-levels a.current, .leaflet-touch .leaflet-control-levels-expanded a,
.leaflet-touch .leaflet-control-sections-expanded a:hover { .leaflet-touch .leaflet-control-levels-expanded a:hover {
opacity:1; opacity:1;
margin-top:0; margin-top:0;
} }
.leaflet-control-sections:not(.leaflet-control-sections-expanded) a.current { .leaflet-control-levels:not(.leaflet-control-levels-expanded) a.current {
border-radius:4px; border-radius:4px;
border-width:0; border-width:0;
} }
.leaflet-control-sections a.current { .leaflet-control-levels a.current {
font-weight:bold; font-weight:bold;
} }
.leaflet-control-sections .leaflet-disabled.current { .leaflet-control-levels .leaflet-disabled.current {
color:#000000; color:#000000;
background-color:#ffffff; background-color:#ffffff;
} }

View file

@ -38,11 +38,11 @@ editor = {
$('body').removeClass('show-map'); $('body').removeClass('show-map');
}); });
editor._section_control = new SectionControl().addTo(editor.map); editor._level_control = new LevelControl().addTo(editor.map);
editor._subsection_control = new SectionControl({addClasses: 'leaflet-control-subsections'}).addTo(editor.map); editor._sublevel_control = new LevelControl({addClasses: 'leaflet-control-sublevels'}).addTo(editor.map);
editor._section_control_container = $(editor._section_control._container); editor._level_control_container = $(editor._level_control._container);
editor._subsection_control_container = $(editor._subsection_control._container); editor._sublevel_control_container = $(editor._sublevel_control._container);
editor.init_geometries(); editor.init_geometries();
}, },
@ -105,30 +105,30 @@ editor = {
}, },
_sidebar_unload: function() { _sidebar_unload: function() {
// unload the sidebar. called on sidebar_get and form submit. // unload the sidebar. called on sidebar_get and form submit.
editor._section_control.disable(); editor._level_control.disable();
editor._subsection_control.disable(); editor._sublevel_control.disable();
$('#sidebar').addClass('loading').find('.content').html(''); $('#sidebar').addClass('loading').find('.content').html('');
editor._cancel_editing(); editor._cancel_editing();
}, },
_fill_section_control: function (section_control, section_list) { _fill_level_control: function (level_control, level_list) {
var sections = section_list.find('a'); var levels = level_list.find('a');
if (sections.length) { if (levels.length) {
var current; var current;
for (var i = 0; i < sections.length; i++) { for (var i = 0; i < levels.length; i++) {
var section = $(sections[i]); var level = $(levels[i]);
section_control.addSection(section.attr('data-id'), section.text(), section.attr('href'), section.is('.current')); level_control.addLevel(level.attr('data-id'), level.text(), level.attr('href'), level.is('.current'));
} }
if (sections.length > 1) { if (levels.length > 1) {
section_control.enable(); level_control.enable();
} else { } else {
section_control.disable(); level_control.disable();
} }
section_control.show() level_control.show()
} else { } else {
section_control.hide(); level_control.hide();
} }
section_control.current_id = parseInt(section_list.attr('data-current-id')); level_control.current_id = parseInt(level_list.attr('data-current-id'));
}, },
_sidebar_loaded: function(data) { _sidebar_loaded: function(data) {
// sidebar was loaded. load the content. check if there are any redirects. call _check_start_editing. // sidebar was loaded. load the content. check if there are any redirects. call _check_start_editing.
@ -157,28 +157,28 @@ editor = {
(editing_id.length ? editing_id.attr('data-editing') : null) (editing_id.length ? editing_id.attr('data-editing') : null)
); );
$('body').addClass('map-enabled'); $('body').addClass('map-enabled');
editor._section_control.clearSections(); editor._level_control.clearLevels();
editor._subsection_control.clearSections(); editor._sublevel_control.clearLevels();
editor._fill_section_control(editor._section_control, content.find('[data-sections]')); editor._fill_level_control(editor._level_control, content.find('[data-levels]'));
editor._fill_section_control(editor._subsection_control, content.find('[data-subsections]')); editor._fill_level_control(editor._sublevel_control, content.find('[data-sublevels]'));
var section_control_offset = $(editor._section_control_container).position(); var level_control_offset = $(editor._level_control_container).position();
var offset_parent = $(editor._section_control_container).offsetParent(); var offset_parent = $(editor._level_control_container).offsetParent();
$(editor._subsection_control._container).css({ $(editor._sublevel_control._container).css({
bottom: offset_parent.height()-section_control_offset.top-editor._section_control_container.height()-parseInt(editor._section_control_container.css('margin-bottom')), bottom: offset_parent.height()-level_control_offset.top-editor._level_control_container.height()-parseInt(editor._level_control_container.css('margin-bottom')),
right: offset_parent.width()-section_control_offset.left right: offset_parent.width()-level_control_offset.left
}); });
} else { } else {
$('body').removeClass('map-enabled').removeClass('show-map'); $('body').removeClass('map-enabled').removeClass('show-map');
editor._section_control.hide(); editor._level_control.hide();
editor._subsection_control.hide(); editor._sublevel_control.hide();
} }
}, },
_sidebar_error: function(data) { _sidebar_error: function(data) {
$('#sidebar').removeClass('loading').find('.content').html('<h3>Error '+data.status+'</h3>'+data.statusText); $('#sidebar').removeClass('loading').find('.content').html('<h3>Error '+data.status+'</h3>'+data.statusText);
editor._section_control.hide(); editor._level_control.hide();
editor._subsection_control.hide(); editor._sublevel_control.hide();
}, },
_sidebar_link_click: function(e) { _sidebar_link_click: function(e) {
// listener for link-clicks in the sidebar. // listener for link-clicks in the sidebar.
@ -314,14 +314,14 @@ editor = {
_get_geometry_style: function (feature) { _get_geometry_style: function (feature) {
// style callback for GeoJSON loader // style callback for GeoJSON loader
var style = editor._get_mapitem_type_style(feature.properties.type); var style = editor._get_mapitem_type_style(feature.properties.type);
if (editor._section_control.current_section_id === editor._subsection_control.current_section_id) { if (editor._level_control.current_level_id === editor._sublevel_control.current_level_id) {
if (editor._subsection_control.section_ids.indexOf(feature.properties.section) >= 0 && editor._section_control.current_section_id !== feature.properties.section) { if (editor._sublevel_control.level_ids.indexOf(feature.properties.level) >= 0 && editor._level_control.current_level_id !== feature.properties.level) {
style.stroke = true; style.stroke = true;
style.weight = 1; style.weight = 1;
style.color = '#ffffff'; style.color = '#ffffff';
} }
} else { } else {
if (editor._subsection_control.current_section_id !== feature.properties.section) { if (editor._sublevel_control.current_level_id !== feature.properties.level) {
style.fillOpacity = 0.5; style.fillOpacity = 0.5;
} }
} }
@ -529,18 +529,18 @@ editor = {
}; };
SectionControl = L.Control.extend({ LevelControl = L.Control.extend({
options: { options: {
position: 'bottomright', position: 'bottomright',
addClasses: '' addClasses: ''
}, },
onAdd: function () { onAdd: function () {
this._container = L.DomUtil.create('div', 'leaflet-control-sections leaflet-bar '+this.options.addClasses); this._container = L.DomUtil.create('div', 'leaflet-control-levels leaflet-bar '+this.options.addClasses);
this._sectionButtons = []; this._levelButtons = [];
//noinspection JSUnusedGlobalSymbols //noinspection JSUnusedGlobalSymbols
this.current_section_id = null; this.current_level_id = null;
this.section_ids = []; this.level_ids = [];
this._disabled = true; this._disabled = true;
this._expanded = false; this._expanded = false;
this.hide(); this.hide();
@ -561,9 +561,9 @@ SectionControl = L.Control.extend({
return this._container; return this._container;
}, },
addSection: function (id, title, href, current) { addLevel: function (id, title, href, current) {
this.section_ids.push(parseInt(id)); this.level_ids.push(parseInt(id));
if (current) this.current_section_id = parseInt(id); if (current) this.current_level_id = parseInt(id);
var link = L.DomUtil.create('a', (current ? 'current' : ''), this._container); var link = L.DomUtil.create('a', (current ? 'current' : ''), this._container);
link.innerHTML = title; link.innerHTML = title;
@ -571,32 +571,32 @@ SectionControl = L.Control.extend({
L.DomEvent L.DomEvent
.on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
.on(link, 'click', this._sectionClick, this); .on(link, 'click', this._levelClick, this);
this._sectionButtons.push(link); this._levelButtons.push(link);
return link; return link;
}, },
clearSections: function() { clearLevels: function() {
this.current_section_id = null; this.current_level_id = null;
this.section_ids = []; this.level_ids = [];
for (var i = 0; i < this._sectionButtons.length; i++) { for (var i = 0; i < this._levelButtons.length; i++) {
L.DomUtil.remove(this._sectionButtons[i]); L.DomUtil.remove(this._levelButtons[i]);
} }
this._sectionButtons = []; this._levelButtons = [];
}, },
disable: function () { disable: function () {
for (var i = 0; i < this._sectionButtons.length; i++) { for (var i = 0; i < this._levelButtons.length; i++) {
L.DomUtil.addClass(this._sectionButtons[i], 'leaflet-disabled'); L.DomUtil.addClass(this._levelButtons[i], 'leaflet-disabled');
} }
this.collapse(); this.collapse();
this._disabled = true; this._disabled = true;
}, },
enable: function () { enable: function () {
for (var i = 0; i < this._sectionButtons.length; i++) { for (var i = 0; i < this._levelButtons.length; i++) {
L.DomUtil.removeClass(this._sectionButtons[i], 'leaflet-disabled'); L.DomUtil.removeClass(this._levelButtons[i], 'leaflet-disabled');
} }
this._disabled = false; this._disabled = false;
}, },
@ -609,7 +609,7 @@ SectionControl = L.Control.extend({
this._container.style.display = ''; this._container.style.display = '';
}, },
_sectionClick: function (e) { _levelClick: function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (!this._expanded) { if (!this._expanded) {
@ -625,13 +625,13 @@ SectionControl = L.Control.extend({
expand: function () { expand: function () {
if (this._disabled) return; if (this._disabled) return;
this._expanded = true; this._expanded = true;
L.DomUtil.addClass(this._container, 'leaflet-control-sections-expanded'); L.DomUtil.addClass(this._container, 'leaflet-control-levels-expanded');
return this; return this;
}, },
collapse: function () { collapse: function () {
this._expanded = false; this._expanded = false;
L.DomUtil.removeClass(this._container, 'leaflet-control-sections-expanded'); L.DomUtil.removeClass(this._container, 'leaflet-control-levels-expanded');
return this; return this;
} }
}); });

View file

@ -1,6 +1,6 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% include 'editor/fragment_sections.html' %} {% include 'editor/fragment_levels.html' %}
<h3>{% blocktrans %}Delete {{ model_title }}{% endblocktrans %}</h3> <h3>{% blocktrans %}Delete {{ model_title }}{% endblocktrans %}</h3>
<form action="{{ path }}" method="post"> <form action="{{ path }}" method="post">

View file

@ -1,7 +1,7 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% include 'editor/fragment_sections.html' %} {% include 'editor/fragment_levels.html' %}
<h3> <h3>
{% if new %} {% if new %}
@ -10,8 +10,8 @@
{% blocktrans %}Edit {{ model_title }}{% endblocktrans %} {% blocktrans %}Edit {{ model_title }}{% endblocktrans %}
{% endif %} {% endif %}
{% if on_top_of %} {% if on_top_of %}
{% with on_top_of.title as on_top_of_section_title %} {% with on_top_of.title as on_top_of_level_title %}
<small>{% blocktrans %}on top of {{ on_top_of_section_title }}{% endblocktrans %}</small> <small>{% blocktrans %}on top of {{ on_top_of_level_title }}{% endblocktrans %}</small>
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</h3> </h3>

View file

@ -0,0 +1,26 @@
{% if levels %}
<ul data-levels>
{% for l in levels %}
<li>
<a data-id="{{ l.pk }}" href="{% if level_as_pk %}{% url level_url pk=l.id %}{% else %}{% url level_url level=l.id %}{% endif %}"{% if level.primary_level == l %} class="current"{% endif %}>{{ l.title }}</a>
</li>
{% endfor %}
</ul>
<ul data-sublevels>
{% for l in level.primary_level.sublevels %}
<li>
<a data-id="{{ l.pk }}" href="{% if level_as_pk %}{% url level_url pk=l.id %}{% else %}{% url level_url level=l.id %}{% endif %}"{% if level == l %} class="current"{% endif %}>{{ l.sublevel_title }}</a>
</li>
{% endfor %}
</ul>
{% elif level %}
<ul data-levels>
<li><a data-id="{{ level.primary_level.pk }}" href="" class="current">{{ level.primary_level.title }}</a></li>
</ul>
<ul data-sublevels>
<li><a data-id="{{ level.pk }}" href="" class="current">{{ level.sublevel_title }}</a></li>
</ul>
{% endif %}
{% if geometry_url %}
<span data-geometry-url="{{ geometry_url }}"></span>
{% endif %}

View file

@ -1,26 +0,0 @@
{% if sections %}
<ul data-sections>
{% for s in sections %}
<li>
<a data-id="{{ s.pk }}" href="{% if section_as_pk %}{% url section_url pk=s.id %}{% else %}{% url section_url section=s.id %}{% endif %}"{% if section.primary_section == s %} class="current"{% endif %}>{{ s.title }}</a>
</li>
{% endfor %}
</ul>
<ul data-subsections>
{% for s in section.primary_section.subsections %}
<li>
<a data-id="{{ s.pk }}" href="{% if section_as_pk %}{% url section_url pk=s.id %}{% else %}{% url section_url section=s.id %}{% endif %}"{% if section == s %} class="current"{% endif %}>{{ s.subsection_title }}</a>
</li>
{% endfor %}
</ul>
{% elif section %}
<ul data-sections>
<li><a data-id="{{ section.primary_section.pk }}" href="" class="current">{{ section.primary_section.title }}</a></li>
</ul>
<ul data-subsections>
<li><a data-id="{{ section.pk }}" href="" class="current">{{ section.subsection_title }}</a></li>
</ul>
{% endif %}
{% if geometry_url %}
<span data-geometry-url="{{ geometry_url }}"></span>
{% endif %}

View file

@ -1,15 +1,15 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
<a class="btn btn-default btn-sm pull-right" accesskey="n" href="{% url 'editor.sections.create' %}"> <a class="btn btn-default btn-sm pull-right" accesskey="n" href="{% url 'editor.levels.create' %}">
{% trans 'Section' as model_title %} {% trans 'Level' as model_title %}
{% blocktrans %}New {{ model_title }}{% endblocktrans %} {% blocktrans %}New {{ model_title }}{% endblocktrans %}
</a> </a>
<h3>{% trans 'Sections' %}</h3> <h3>{% trans 'Level' %}</h3>
<div class="list-group"> <div class="list-group">
{% for s in sections %} {% for l in levels %}
<a href="{% url 'editor.sections.detail' pk=s.pk %}" class="list-group-item"> <a href="{% url 'editor.levels.detail' pk=l.pk %}" class="list-group-item">
{{ s.title }} {{ l.title }}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -0,0 +1,42 @@
{% load bootstrap3 %}
{% load i18n %}
{% include 'editor/fragment_levels.html' %}
<a class="btn btn-default btn-sm pull-right" accesskey="e" href="{% url 'editor.levels.edit' pk=level.pk %}">
{% trans 'Level' as model_title %}
{% blocktrans %}Edit {{ model_title }}{% endblocktrans %}
</a>
<h3>
<h3>
{{ level.title }}
{% if level.on_top_of != None %}
{% with level.on_top_of.title as on_top_of_level_title %}
<small>{% blocktrans %}on top of {{ on_top_of_level_title }}{% endblocktrans %}</small>
{% endwith %}
{% endif %}
</h3>
<p>
{% if level.on_top_of == None %}
<a href="{% url 'editor.index' %}">&laquo; {% trans 'back to overview' %}</a>
{% else %}
<a href="{% url 'editor.levels.detail' pk=level.on_top_of.pk %}">&laquo; {% trans 'back to parent level' %}</a>
{% endif %}
</p>
{% include 'editor/fragment_child_models.html' %}
<div class="clearfix"></div>
{% if level.on_top_of == None %}
<a class="btn btn-default btn-sm pull-right" accesskey="n" href="{% url 'editor.levels_on_top.create' on_top_of=level.pk %}">
{% blocktrans %}New {{ model_title }}{% endblocktrans %}
</a>
<h3>{% trans 'Levels on top' %}</h3>
<div class="list-group">
{% for l in levels_on_top %}
<a href="{% url 'editor.levels.detail' pk=l.pk %}" class="list-group-item">
{{ l.title }}
</a>
{% endfor %}
</div>
{% endif %}

View file

@ -1,16 +1,16 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% include 'editor/fragment_sections.html' %} {% include 'editor/fragment_levels.html' %}
<a class="btn btn-default btn-sm pull-right" accesskey="n" href="{{ create_url }}"> <a class="btn btn-default btn-sm pull-right" accesskey="n" href="{{ create_url }}">
{% blocktrans %}New {{ model_title }}{% endblocktrans %} {% blocktrans %}New {{ model_title }}{% endblocktrans %}
</a> </a>
<h3> <h3>
{% blocktrans %}{{ model_title_plural }}{% endblocktrans %} {% blocktrans %}{{ model_title_plural }}{% endblocktrans %}
{% if section %} {% if level %}
{% with section.title as section_title %} {% with level.title as level_title %}
<small>{% blocktrans %}in section {{ section_title }}{% endblocktrans %}</small> <small>{% blocktrans %}on level {{ level_title }}{% endblocktrans %}</small>
{% endwith %} {% endwith %}
{% endif %} {% endif %}
{% if space %} {% if space %}

View file

@ -1,41 +0,0 @@
{% load bootstrap3 %}
{% load i18n %}
{% include 'editor/fragment_sections.html' %}
<a class="btn btn-default btn-sm pull-right" accesskey="e" href="{% url 'editor.sections.edit' pk=section.pk %}">
{% trans 'Section' as model_title %}
{% blocktrans %}Edit {{ model_title }}{% endblocktrans %}
</a>
<h3>
{{ section.title }}
{% if section.on_top_of != None %}
{% with section.on_top_of.title as on_top_of_section_title %}
<small>{% blocktrans %}on top of {{ on_top_of_section_title }}{% endblocktrans %}</small>
{% endwith %}
{% endif %}
</h3>
<p>
{% if section.on_top_of == None %}
<a href="{% url 'editor.index' %}">&laquo; {% trans 'back to overview' %}</a>
{% else %}
<a href="{% url 'editor.sections.detail' pk=section.on_top_of.pk %}">&laquo; {% trans 'back to parent section' %}</a>
{% endif %}
</p>
{% include 'editor/fragment_child_models.html' %}
<div class="clearfix"></div>
{% if section.on_top_of == None %}
<a class="btn btn-default btn-sm pull-right" accesskey="n" href="{% url 'editor.sections_on_top.create' on_top_of=section.pk %}">
{% blocktrans %}New {{ model_title }}{% endblocktrans %}
</a>
<h3>{% trans 'Sections on top' %}</h3>
<div class="list-group">
{% for s in sections_on_top %}
<a href="{% url 'editor.sections.detail' pk=s.pk %}" class="list-group-item">
{{ s.title }}
</a>
{% endfor %}
</div>
{% endif %}

View file

@ -1,14 +1,14 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% include 'editor/fragment_sections.html' %} {% include 'editor/fragment_levels.html' %}
<a class="btn btn-default btn-sm pull-right" accesskey="e" href="{% url 'editor.spaces.edit' section=space.section.pk pk=space.pk %}"> <a class="btn btn-default btn-sm pull-right" accesskey="e" href="{% url 'editor.spaces.edit' level=space.level.pk pk=space.pk %}">
{% trans 'Space' as model_title %} {% trans 'Space' as model_title %}
{% blocktrans %}Edit {{ model_title }}{% endblocktrans %} {% blocktrans %}Edit {{ model_title }}{% endblocktrans %}
</a> </a>
<h3>{{ space.title }}</h3> <h3>{{ space.title }}</h3>
<p> <p>
<a href="{% url 'editor.spaces.list' section=space.section.pk %}">&laquo; {% trans 'back to overview' %}</a> <a href="{% url 'editor.spaces.list' level=space.level.pk %}">&laquo; {% trans 'back to overview' %}</a>
</p> </p>
{% include 'editor/fragment_child_models.html' %} {% include 'editor/fragment_child_models.html' %}

View file

@ -1,7 +1,7 @@
from django.apps import apps from django.apps import apps
from django.conf.urls import url from django.conf.urls import url
from c3nav.editor.views import edit, list_objects, main_index, section_detail, space_detail from c3nav.editor.views import edit, level_detail, list_objects, main_index, space_detail
def add_editor_urls(model_name, parent_model_name=None, with_list=True, explicit_edit=False): def add_editor_urls(model_name, parent_model_name=None, with_list=True, explicit_edit=False):
@ -30,17 +30,17 @@ def add_editor_urls(model_name, parent_model_name=None, with_list=True, explicit
urlpatterns = [ urlpatterns = [
url(r'^$', main_index, name='editor.index'), url(r'^$', main_index, name='editor.index'),
url(r'^sections/(?P<pk>[0-9]+)/$', section_detail, name='editor.sections.detail'), url(r'^levels/(?P<pk>[0-9]+)/$', level_detail, name='editor.levels.detail'),
url(r'^sections/(?P<section>[0-9]+)/spaces/(?P<pk>[0-9]+)/$', space_detail, name='editor.spaces.detail'), url(r'^levels/(?P<level>[0-9]+)/spaces/(?P<pk>[0-9]+)/$', space_detail, name='editor.spaces.detail'),
url(r'^sections/(?P<on_top_of>[0-9]+)/sections_on_top/create$', edit, name='editor.sections_on_top.create', url(r'^levels/(?P<on_top_of>[0-9]+)/levels_on_top/create$', edit, name='editor.levels_on_top.create',
kwargs={'model': 'Section'}), kwargs={'model': 'Level'}),
] ]
urlpatterns.extend(add_editor_urls('Section', with_list=False, explicit_edit=True)) urlpatterns.extend(add_editor_urls('Level', with_list=False, explicit_edit=True))
urlpatterns.extend(add_editor_urls('LocationGroup')) urlpatterns.extend(add_editor_urls('LocationGroup'))
urlpatterns.extend(add_editor_urls('Source')) urlpatterns.extend(add_editor_urls('Source'))
urlpatterns.extend(add_editor_urls('Building', 'Section')) urlpatterns.extend(add_editor_urls('Building', 'Level'))
urlpatterns.extend(add_editor_urls('Space', 'Section', explicit_edit=True)) urlpatterns.extend(add_editor_urls('Space', 'Level', explicit_edit=True))
urlpatterns.extend(add_editor_urls('Door', 'Section')) urlpatterns.extend(add_editor_urls('Door', 'Level'))
urlpatterns.extend(add_editor_urls('Hole', 'Space')) urlpatterns.extend(add_editor_urls('Hole', 'Space'))
urlpatterns.extend(add_editor_urls('Area', 'Space')) urlpatterns.extend(add_editor_urls('Area', 'Space'))
urlpatterns.extend(add_editor_urls('Stair', 'Space')) urlpatterns.extend(add_editor_urls('Stair', 'Space'))

View file

@ -10,7 +10,7 @@ from django.urls import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from c3nav.mapdata.models import Section, Space from c3nav.mapdata.models import Level, Space
from c3nav.mapdata.models.base import EDITOR_FORM_MODELS from c3nav.mapdata.models.base import EDITOR_FORM_MODELS
@ -39,7 +39,7 @@ def child_model(model_name, kwargs=None, parent=None):
@sidebar_view @sidebar_view
def main_index(request): def main_index(request):
return render(request, 'editor/index.html', { return render(request, 'editor/index.html', {
'sections': Section.objects.filter(on_top_of__isnull=True), 'levels': Level.objects.filter(on_top_of__isnull=True),
'child_models': [ 'child_models': [
child_model('LocationGroup'), child_model('LocationGroup'),
child_model('Source'), child_model('Source'),
@ -48,28 +48,28 @@ def main_index(request):
@sidebar_view @sidebar_view
def section_detail(request, pk): def level_detail(request, pk):
section = get_object_or_404(Section.objects.select_related('on_top_of'), pk=pk) level = get_object_or_404(Level.objects.select_related('on_top_of'), pk=pk)
return render(request, 'editor/section.html', { return render(request, 'editor/level.html', {
'sections': Section.objects.filter(on_top_of__isnull=True), 'levels': Level.objects.filter(on_top_of__isnull=True),
'section': section, 'level': level,
'section_url': 'editor.sections.detail', 'level_url': 'editor.levels.detail',
'section_as_pk': True, 'level_as_pk': True,
'child_models': [child_model(model_name, kwargs={'section': pk}, parent=section) 'child_models': [child_model(model_name, kwargs={'level': pk}, parent=level)
for model_name in ('Building', 'Space', 'Door')], for model_name in ('Building', 'Space', 'Door')],
'sections_on_top': section.sections_on_top.all(), 'levels_on_top': level.levels_on_top.all(),
'geometry_url': '/api/editor/geometries/?section='+str(section.primary_section_pk), 'geometry_url': '/api/editor/geometries/?level='+str(level.primary_level_pk),
}) })
@sidebar_view @sidebar_view
def space_detail(request, section, pk): def space_detail(request, level, pk):
space = get_object_or_404(Space, section__id=section, pk=pk) space = get_object_or_404(Space, level__id=level, pk=pk)
return render(request, 'editor/space.html', { return render(request, 'editor/space.html', {
'section': space.section, 'level': space.level,
'space': space, 'space': space,
'child_models': [child_model(model_name, kwargs={'space': pk}, parent=space) 'child_models': [child_model(model_name, kwargs={'space': pk}, parent=space)
@ -79,7 +79,7 @@ def space_detail(request, section, pk):
@sidebar_view @sidebar_view
def edit(request, pk=None, model=None, section=None, space=None, on_top_of=None, explicit_edit=False): def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, explicit_edit=False):
model = EDITOR_FORM_MODELS[model] model = EDITOR_FORM_MODELS[model]
related_name = model._meta.default_related_name related_name = model._meta.default_related_name
@ -87,19 +87,19 @@ def edit(request, pk=None, model=None, section=None, space=None, on_top_of=None,
if pk is not None: if pk is not None:
# Edit existing map item # Edit existing map item
kwargs = {'pk': pk} kwargs = {'pk': pk}
if section is not None: if level is not None:
kwargs.update({'section__id': section}) kwargs.update({'level__id': level})
elif space is not None: elif space is not None:
kwargs.update({'space__id': space}) kwargs.update({'space__id': space})
obj = get_object_or_404(model, **kwargs) obj = get_object_or_404(model, **kwargs)
if False: # todo can access if False: # todo can access
raise PermissionDenied raise PermissionDenied
elif section is not None: elif level is not None:
section = get_object_or_404(Section, pk=section) level = get_object_or_404(Level, pk=level)
elif space is not None: elif space is not None:
space = get_object_or_404(Space, pk=space) space = get_object_or_404(Space, pk=space)
elif on_top_of is not None: elif on_top_of is not None:
on_top_of = get_object_or_404(Section.objects.filter(on_top_of__isnull=True), pk=on_top_of) on_top_of = get_object_or_404(Level.objects.filter(on_top_of__isnull=True), pk=on_top_of)
new = obj is None new = obj is None
# noinspection PyProtectedMember # noinspection PyProtectedMember
@ -117,55 +117,55 @@ def edit(request, pk=None, model=None, section=None, space=None, on_top_of=None,
'geomtype': model._meta.get_field('geometry').geomtype, 'geomtype': model._meta.get_field('geometry').geomtype,
}) })
if model == Section: if model == Level:
ctx.update({ ctx.update({
'section': obj, 'level': obj,
'back_url': reverse('editor.index') if new else reverse('editor.sections.detail', kwargs={'pk': pk}), 'back_url': reverse('editor.index') if new else reverse('editor.levels.detail', kwargs={'pk': pk}),
}) })
if not new: if not new:
ctx.update({ ctx.update({
'geometry_url': '/api/editor/geometries/?section='+str(obj.primary_section_pk), 'geometry_url': '/api/editor/geometries/?level='+str(obj.primary_level_pk),
'on_top_of': obj.on_top_of, 'on_top_of': obj.on_top_of,
}) })
elif on_top_of: elif on_top_of:
ctx.update({ ctx.update({
'geometry_url': '/api/editor/geometries/?section=' + str(on_top_of.pk), 'geometry_url': '/api/editor/geometries/?level=' + str(on_top_of.pk),
'on_top_of': on_top_of, 'on_top_of': on_top_of,
'back_url': reverse('editor.sections.detail', kwargs={'pk': on_top_of.pk}), 'back_url': reverse('editor.levels.detail', kwargs={'pk': on_top_of.pk}),
}) })
elif model == Space and not new: elif model == Space and not new:
section = obj.section level = obj.level
ctx.update({ ctx.update({
'section': obj.section, 'level': obj.level,
'back_url': reverse('editor.spaces.detail', kwargs={'section': obj.section.pk, 'pk': pk}), 'back_url': reverse('editor.spaces.detail', kwargs={'level': obj.level.pk, 'pk': pk}),
'geometry_url': '/api/editor/geometries/?space='+pk, 'geometry_url': '/api/editor/geometries/?space='+pk,
}) })
elif model == Space and new: elif model == Space and new:
ctx.update({ ctx.update({
'section': section, 'level': level,
'back_url': reverse('editor.spaces.list', kwargs={'section': section.pk}), 'back_url': reverse('editor.spaces.list', kwargs={'level': level.pk}),
'geometry_url': '/api/editor/geometries/?section='+str(section.primary_section_pk), 'geometry_url': '/api/editor/geometries/?level='+str(level.primary_level_pk),
}) })
elif hasattr(model, 'section'): elif hasattr(model, 'level'):
if obj: if obj:
section = obj.section level = obj.level
ctx.update({ ctx.update({
'section': section, 'level': level,
'back_url': reverse('editor.'+related_name+'.list', kwargs={'section': section.pk}), 'back_url': reverse('editor.'+related_name+'.list', kwargs={'level': level.pk}),
'geometry_url': '/api/editor/geometries/?section='+str(section.primary_section_pk), 'geometry_url': '/api/editor/geometries/?level='+str(level.primary_level_pk),
}) })
elif hasattr(model, 'space'): elif hasattr(model, 'space'):
if obj: if obj:
space = obj.space space = obj.space
ctx.update({ ctx.update({
'section': space.section, 'level': space.level,
'back_url': reverse('editor.'+related_name+'.list', kwargs={'space': space.pk}), 'back_url': reverse('editor.'+related_name+'.list', kwargs={'space': space.pk}),
'geometry_url': '/api/editor/geometries/?space='+str(space.pk), 'geometry_url': '/api/editor/geometries/?space='+str(space.pk),
}) })
else: else:
kwargs = {} kwargs = {}
if section is not None: if level is not None:
kwargs.update({'section': section}) kwargs.update({'level': level})
elif space is not None: elif space is not None:
kwargs.update({'space': space}) kwargs.update({'space': space})
@ -181,12 +181,12 @@ def edit(request, pk=None, model=None, section=None, space=None, on_top_of=None,
# todo: suggest changes # todo: suggest changes
raise NotImplementedError raise NotImplementedError
obj.delete() obj.delete()
if model == Section: if model == Level:
if obj.on_top_of_id is not None: if obj.on_top_of_id is not None:
return redirect(reverse('editor.sections.detail', kwargs={'pk': obj.on_top_of_id})) return redirect(reverse('editor.levels.detail', kwargs={'pk': obj.on_top_of_id}))
return redirect(reverse('editor.index')) return redirect(reverse('editor.index'))
elif model == Space: elif model == Space:
return redirect(reverse('editor.spaces.list', kwargs={'section': obj.section.pk})) return redirect(reverse('editor.spaces.list', kwargs={'level': obj.level.pk}))
return redirect(ctx['back_url']) return redirect(ctx['back_url'])
ctx['obj_title'] = obj.title ctx['obj_title'] = obj.title
return render(request, 'editor/delete.html', ctx) return render(request, 'editor/delete.html', ctx)
@ -213,8 +213,8 @@ def edit(request, pk=None, model=None, section=None, space=None, on_top_of=None,
# todo: suggest changes # todo: suggest changes
raise NotImplementedError raise NotImplementedError
if section is not None: if level is not None:
obj.section = section obj.level = level
if space is not None: if space is not None:
obj.space = space obj.space = space
@ -237,7 +237,7 @@ def edit(request, pk=None, model=None, section=None, space=None, on_top_of=None,
@sidebar_view @sidebar_view
def list_objects(request, model=None, section=None, space=None, explicit_edit=False): def list_objects(request, model=None, level=None, space=None, explicit_edit=False):
model = EDITOR_FORM_MODELS[model] model = EDITOR_FORM_MODELS[model]
if not request.resolver_match.url_name.endswith('.list'): if not request.resolver_match.url_name.endswith('.list'):
raise ValueError('url_name does not end with .list') raise ValueError('url_name does not end with .list')
@ -254,25 +254,25 @@ def list_objects(request, model=None, section=None, space=None, explicit_edit=Fa
queryset = model.objects.all().order_by('id') queryset = model.objects.all().order_by('id')
reverse_kwargs = {} reverse_kwargs = {}
if section is not None: if level is not None:
reverse_kwargs['section'] = section reverse_kwargs['level'] = level
section = get_object_or_404(Section, pk=section) level = get_object_or_404(Level, pk=level)
queryset = queryset.filter(section=section) queryset = queryset.filter(level=level)
ctx.update({ ctx.update({
'back_url': reverse('editor.sections.detail', kwargs={'pk': section.pk}), 'back_url': reverse('editor.levels.detail', kwargs={'pk': level.pk}),
'back_title': _('back to section'), 'back_title': _('back to level'),
'sections': Section.objects.filter(on_top_of__isnull=True), 'levels': Level.objects.filter(on_top_of__isnull=True),
'section': section, 'level': level,
'section_url': request.resolver_match.url_name, 'level_url': request.resolver_match.url_name,
'geometry_url': '/api/editor/geometries/?section='+str(section.primary_section_pk), 'geometry_url': '/api/editor/geometries/?level='+str(level.primary_level_pk),
}) })
elif space is not None: elif space is not None:
reverse_kwargs['space'] = space reverse_kwargs['space'] = space
space = get_object_or_404(Space, pk=space) space = get_object_or_404(Space, pk=space)
queryset = queryset.filter(space=space) queryset = queryset.filter(space=space)
ctx.update({ ctx.update({
'section': space.section, 'level': space.level,
'back_url': reverse('editor.spaces.detail', kwargs={'section': space.section.pk, 'pk': space.pk}), 'back_url': reverse('editor.spaces.detail', kwargs={'level': space.level.pk, 'pk': space.pk}),
'back_title': _('back to space'), 'back_title': _('back to space'),
'geometry_url': '/api/editor/geometries/?space='+str(space.pk), 'geometry_url': '/api/editor/geometries/?space='+str(space.pk),
}) })

View file

@ -12,24 +12,24 @@ from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ReadOnlyModelViewSet
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.level import LEVEL_MODELS
from c3nav.mapdata.models.geometry.space import SPACE_MODELS, Area, Column, LineObstacle, Obstacle, Point, Stair from c3nav.mapdata.models.geometry.space import SPACE_MODELS, Area, Column, LineObstacle, Obstacle, Point, Stair
from c3nav.mapdata.models.level import Level
from c3nav.mapdata.models.locations import LOCATION_MODELS, Location, LocationRedirect, LocationSlug from c3nav.mapdata.models.locations import LOCATION_MODELS, Location, LocationRedirect, LocationSlug
from c3nav.mapdata.models.section import Section
class MapdataViewSet(ReadOnlyModelViewSet): class MapdataViewSet(ReadOnlyModelViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
qs = self.get_queryset() qs = self.get_queryset()
geometry = ('geometry' in request.GET) geometry = ('geometry' in request.GET)
if qs.model in SECTION_MODELS and 'section' in request.GET: if qs.model in LEVEL_MODELS and 'level' in request.GET:
if not request.GET['section'].isdigit(): if not request.GET['level'].isdigit():
raise ValidationError(detail={'detail': _('%s is not an integer.') % 'section'}) raise ValidationError(detail={'detail': _('%s is not an integer.') % 'level'})
try: try:
section = Section.objects.get(pk=request.GET['section']) level = Level.objects.get(pk=request.GET['level'])
except Section.DoesNotExist: except Level.DoesNotExist:
raise NotFound(detail=_('section not found.')) raise NotFound(detail=_('level not found.'))
qs = qs.filter(section=section) qs = qs.filter(level=level)
if qs.model in SPACE_MODELS and 'space' in request.GET: if qs.model in SPACE_MODELS and 'space' in request.GET:
if not request.GET['space'].isdigit(): if not request.GET['space'].isdigit():
raise ValidationError(detail={'detail': _('%s is not an integer.') % 'space'}) raise ValidationError(detail={'detail': _('%s is not an integer.') % 'space'})
@ -38,17 +38,17 @@ class MapdataViewSet(ReadOnlyModelViewSet):
except Space.DoesNotExist: except Space.DoesNotExist:
raise NotFound(detail=_('space not found.')) raise NotFound(detail=_('space not found.'))
qs = qs.filter(space=space) qs = qs.filter(space=space)
if qs.model == Section and 'on_top_of' in request.GET: if qs.model == Level and 'on_top_of' in request.GET:
if request.GET['on_top_of'] == 'null': if request.GET['on_top_of'] == 'null':
qs = qs.filter(on_top_of__isnull=False) qs = qs.filter(on_top_of__isnull=False)
else: else:
if not request.GET['on_top_of'].isdigit(): if not request.GET['on_top_of'].isdigit():
raise ValidationError(detail={'detail': _('%s is not null or an integer.') % 'on_top_of'}) raise ValidationError(detail={'detail': _('%s is not null or an integer.') % 'on_top_of'})
try: try:
section = Section.objects.get(pk=request.GET['on_top_of']) level = Level.objects.get(pk=request.GET['on_top_of'])
except Section.DoesNotExist: except Level.DoesNotExist:
raise NotFound(detail=_('section not found.')) raise NotFound(detail=_('level not found.'))
qs = qs.filter(on_top_of=section) qs = qs.filter(on_top_of=level)
return Response([obj.serialize(geometry=geometry) for obj in qs.order_by('id')]) return Response([obj.serialize(geometry=geometry) for obj in qs.order_by('id')])
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
@ -61,28 +61,28 @@ class MapdataViewSet(ReadOnlyModelViewSet):
]) ])
class SectionViewSet(MapdataViewSet): class LevelViewSet(MapdataViewSet):
""" Add ?on_top_of=null or ?on_top_of=<id> to filter by on_top_of. """ """ Add ?on_top_of=null or ?on_top_of=<id> to filter by on_top_of. """
queryset = Section.objects.all() queryset = Level.objects.all()
@list_route(methods=['get']) @list_route(methods=['get'])
def geometrytypes(self, request): def geometrytypes(self, request):
return self.list_types(SECTION_MODELS) return self.list_types(LEVEL_MODELS)
@detail_route(methods=['get']) @detail_route(methods=['get'])
def svg(self, requests, pk=None): def svg(self, requests, pk=None):
section = self.get_object() level = self.get_object()
response = HttpResponse(section.render_svg(), 'image/svg+xml') response = HttpResponse(level.render_svg(), 'image/svg+xml')
return response return response
class BuildingViewSet(MapdataViewSet): class BuildingViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?section=<id> to filter by section. """ """ Add ?geometry=1 to get geometries, add ?level=<id> to filter by level. """
queryset = Building.objects.all() queryset = Building.objects.all()
class SpaceViewSet(MapdataViewSet): class SpaceViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?section=<id> to filter by section. """ """ Add ?geometry=1 to get geometries, add ?level=<id> to filter by level. """
queryset = Space.objects.all() queryset = Space.objects.all()
@list_route(methods=['get']) @list_route(methods=['get'])
@ -91,7 +91,7 @@ class SpaceViewSet(MapdataViewSet):
class DoorViewSet(MapdataViewSet): class DoorViewSet(MapdataViewSet):
""" Add ?geometry=1 to get geometries, add ?section=<id> to filter by section. """ """ Add ?geometry=1 to get geometries, add ?level=<id> to filter by level. """
queryset = Door.objects.all() queryset = Door.objects.all()

View file

@ -33,8 +33,8 @@ class MapdataConfig(AppConfig):
from c3nav.mapdata.models.locations import Location, LOCATION_MODELS from c3nav.mapdata.models.locations import Location, LOCATION_MODELS
LOCATION_MODELS.extend(self._get_submodels(Location)) LOCATION_MODELS.extend(self._get_submodels(Location))
from c3nav.mapdata.models.geometry.section import SectionGeometryMixin, SECTION_MODELS from c3nav.mapdata.models.geometry.level import LevelGeometryMixin, LEVEL_MODELS
SECTION_MODELS.extend(self._get_submodels(SectionGeometryMixin)) LEVEL_MODELS.extend(self._get_submodels(LevelGeometryMixin))
from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin, SPACE_MODELS from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin, SPACE_MODELS
SPACE_MODELS.extend(self._get_submodels(SpaceGeometryMixin)) SPACE_MODELS.extend(self._get_submodels(SpaceGeometryMixin))

View file

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-11 12:06
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0011_outside_only_outside'),
]
operations = [
migrations.RenameModel(
old_name='Section',
new_name='Level',
),
migrations.AlterModelOptions(
name='level',
options={'ordering': ['altitude'], 'verbose_name': 'Level', 'verbose_name_plural': 'Levels'},
),
migrations.RenameField(
model_name='building',
old_name='section',
new_name='level',
),
migrations.RenameField(
model_name='door',
old_name='section',
new_name='level',
),
migrations.RenameField(
model_name='space',
old_name='section',
new_name='level',
),
migrations.AlterField(
model_name='level',
name='altitude',
field=models.DecimalField(decimal_places=2, max_digits=6, unique=True, verbose_name='level altitude'),
),
migrations.AlterField(
model_name='level',
name='groups',
field=models.ManyToManyField(blank=True, related_name='levels', to='mapdata.LocationGroup',
verbose_name='Location Groups'),
),
migrations.AlterField(
model_name='level',
name='locationslug_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, related_name='levels', serialize=False,
to='mapdata.LocationSlug'),
),
migrations.AlterField(
model_name='level',
name='on_top_of',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE,
related_name='levels_on_top', to='mapdata.Level', verbose_name='on top of'),
),
migrations.AlterField(
model_name='building',
name='level',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='buildings',
to='mapdata.Level', verbose_name='level'),
),
migrations.AlterField(
model_name='door',
name='level',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='doors',
to='mapdata.Level', verbose_name='level'),
),
migrations.AlterField(
model_name='space',
name='level',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='spaces',
to='mapdata.Level', verbose_name='level'),
),
]

View file

@ -1,5 +1,5 @@
from .section import Section # noqa from .level import Level # noqa
from .source import Source # noqa from .source import Source # noqa
from c3nav.mapdata.models.geometry.section import Building, Space, Door # noqa from c3nav.mapdata.models.geometry.level import Building, Space, Door # noqa
from c3nav.mapdata.models.geometry.space import Area, Stair, Obstacle, LineObstacle, Hole # noqa from c3nav.mapdata.models.geometry.space import Area, Stair, Obstacle, LineObstacle, Hole # noqa
from .locations import Location, LocationSlug, LocationGroup # noqa from .locations import Location, LocationSlug, LocationGroup # noqa

View file

@ -57,7 +57,7 @@ class BoundsMixin(SerializableMixin, models.Model):
return ((float(result['bottom__min']), float(result['left__min'])), return ((float(result['bottom__min']), float(result['left__min'])),
(float(result['top__max']), float(result['right__max']))) (float(result['top__max']), float(result['right__max'])))
def _serialize(self, section=True, **kwargs): def _serialize(self, level=True, **kwargs):
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
result['bounds'] = self.bounds result['bounds'] = self.bounds
return result return result

View file

@ -5,18 +5,18 @@ from c3nav.mapdata.fields import GeometryField
from c3nav.mapdata.models.geometry.base import GeometryMixin from c3nav.mapdata.models.geometry.base import GeometryMixin
from c3nav.mapdata.models.locations import SpecificLocation from c3nav.mapdata.models.locations import SpecificLocation
SECTION_MODELS = [] LEVEL_MODELS = []
class SectionGeometryMixin(GeometryMixin): class LevelGeometryMixin(GeometryMixin):
section = models.ForeignKey('mapdata.Section', on_delete=models.CASCADE, verbose_name=_('section')) level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'))
class Meta: class Meta:
abstract = True abstract = True
def get_geojson_properties(self) -> dict: def get_geojson_properties(self) -> dict:
result = super().get_geojson_properties() result = super().get_geojson_properties()
result['section'] = self.section_id result['level'] = self.level_id
if hasattr(self, 'get_color'): if hasattr(self, 'get_color'):
color = self.get_color() color = self.get_color()
if color: if color:
@ -25,14 +25,14 @@ class SectionGeometryMixin(GeometryMixin):
result['opacity'] = self.opacity result['opacity'] = self.opacity
return result return result
def _serialize(self, section=True, **kwargs): def _serialize(self, level=True, **kwargs):
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
if section: if level:
result['section'] = self.section.id result['level'] = self.level_id
return result return result
class Building(SectionGeometryMixin, models.Model): class Building(LevelGeometryMixin, models.Model):
""" """
The outline of a building on a specific level The outline of a building on a specific level
""" """
@ -44,9 +44,9 @@ class Building(SectionGeometryMixin, models.Model):
default_related_name = 'buildings' default_related_name = 'buildings'
class Space(SpecificLocation, SectionGeometryMixin, models.Model): class Space(SpecificLocation, LevelGeometryMixin, models.Model):
""" """
An accessible space. Shouldn't overlap with spaces on same section. An accessible space. Shouldn't overlap with spaces on the same level.
""" """
CATEGORIES = ( CATEGORIES = (
('normal', _('normal')), ('normal', _('normal')),
@ -81,7 +81,7 @@ class Space(SpecificLocation, SectionGeometryMixin, models.Model):
return color return color
class Door(SectionGeometryMixin, models.Model): class Door(LevelGeometryMixin, models.Model):
""" """
A connection between two spaces A connection between two spaces
""" """

View file

@ -11,18 +11,18 @@ from c3nav.mapdata.models.locations import SpecificLocation
from c3nav.mapdata.utils.svg import SVGImage from c3nav.mapdata.utils.svg import SVGImage
class Section(SpecificLocation, EditorFormMixin, models.Model): class Level(SpecificLocation, EditorFormMixin, models.Model):
""" """
A map section like a level A map level
""" """
altitude = models.DecimalField(_('section altitude'), null=False, unique=True, max_digits=6, decimal_places=2) altitude = models.DecimalField(_('level altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
on_top_of = models.ForeignKey('mapdata.Section', null=True, on_delete=models.CASCADE, on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE,
related_name='sections_on_top', verbose_name=_('on top of')) related_name='levels_on_top', verbose_name=_('on top of'))
class Meta: class Meta:
verbose_name = _('Section') verbose_name = _('Level')
verbose_name_plural = _('Sections') verbose_name_plural = _('Levels')
default_related_name = 'sections' default_related_name = 'levels'
ordering = ['altitude'] ordering = ['altitude']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -31,32 +31,32 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
def lower(self): def lower(self):
if self.on_top_of is not None: if self.on_top_of is not None:
raise TypeError raise TypeError
return Section.objects.filter(altitude__lt=self.altitude, on_top_of__isnull=True).order_by('-altitude') return Level.objects.filter(altitude__lt=self.altitude, on_top_of__isnull=True).order_by('-altitude')
def higher(self): def higher(self):
if self.on_top_of is not None: if self.on_top_of is not None:
raise TypeError raise TypeError
return Section.objects.filter(altitude__gt=self.altitude, on_top_of__isnull=True).order_by('altitude') return Level.objects.filter(altitude__gt=self.altitude, on_top_of__isnull=True).order_by('altitude')
@property @property
def subsections(self): def sublevels(self):
if self.on_top_of is not None: if self.on_top_of is not None:
raise TypeError raise TypeError
return chain((self, ), self.sections_on_top.all()) return chain((self, ), self.levels_on_top.all())
@property @property
def subsection_title(self): def sublevel_title(self):
return '-' if self.on_top_of_id is None else self.title return '-' if self.on_top_of_id is None else self.title
@property @property
def primary_section(self): def primary_level(self):
return self if self.on_top_of_id is None else self.on_top_of return self if self.on_top_of_id is None else self.on_top_of
@property @property
def primary_section_pk(self): def primary_level_pk(self):
return self.pk if self.on_top_of_id is None else self.on_top_of_id return self.pk if self.on_top_of_id is None else self.on_top_of_id
def _serialize(self, section=True, **kwargs): def _serialize(self, level=True, **kwargs):
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
result['altitude'] = float(str(self.altitude)) result['altitude'] = float(str(self.altitude))
return result return result
@ -109,10 +109,10 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
# draw space background # draw space background
doors = self.doors.all() doors = self.doors.all()
door_geometries = cascaded_union(tuple(d.geometry for d in doors)) door_geometries = cascaded_union(tuple(d.geometry for d in doors))
section_geometry = cascaded_union((space_geometries, building_geometries, door_geometries)) level_geometry = cascaded_union((space_geometries, building_geometries, door_geometries))
section_geometry = section_geometry.difference(hole_geometries) level_geometry = level_geometry.difference(hole_geometries)
section_clip = svg.register_geometry(section_geometry, defid='section', as_clip_path=True) level_clip = svg.register_geometry(level_geometry, defid='level', as_clip_path=True)
svg.add_geometry(fill_color='#d1d1d1', clip_path=section_clip) svg.add_geometry(fill_color='#d1d1d1', clip_path=level_clip)
# color in spaces # color in spaces
spaces_by_color = {} spaces_by_color = {}
@ -134,7 +134,7 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
if effects: if effects:
wall_dilated_geometry = wall_geometry.buffer(0.7, join_style=JOIN_STYLE.mitre) wall_dilated_geometry = wall_geometry.buffer(0.7, join_style=JOIN_STYLE.mitre)
svg.add_geometry(wall_dilated_geometry, fill_color='#000000', opacity=0.1, filter='wallblur', svg.add_geometry(wall_dilated_geometry, fill_color='#000000', opacity=0.1, filter='wallblur',
clip_path=section_clip) clip_path=level_clip)
for space in spaces: for space in spaces:
self._render_space_inventory(svg, space) self._render_space_inventory(svg, space)

View file

@ -13,8 +13,8 @@ LOCATION_MODELS = []
class LocationSlug(SerializableMixin, models.Model): class LocationSlug(SerializableMixin, models.Model):
LOCATION_TYPE_CODES = { LOCATION_TYPE_CODES = {
'Section': 'se', 'Level': 'l',
'Space': 'sp', 'Space': 's',
'Area': 'a', 'Area': 'a',
'Point': 'p', 'Point': 'p',
'LocationGroup': 'g' 'LocationGroup': 'g'

View file

@ -20,7 +20,7 @@ class Source(EditorFormMixin, BoundsMixin, models.Model):
def title(self): def title(self):
return self.name return self.name
def _serialize(self, section=True, **kwargs): def _serialize(self, level=True, **kwargs):
result = super()._serialize(**kwargs) result = super()._serialize(**kwargs)
result['name'] = self.name result['name'] = self.name
return result return result

View file

@ -9,8 +9,8 @@ from django.conf import settings
from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._shortest_path import shortest_path
from scipy.sparse.csgraph._tools import csgraph_from_dense from scipy.sparse.csgraph._tools import csgraph_from_dense
from c3nav.mapdata.models.level import Level
from c3nav.mapdata.models.locations import Location, LocationGroup from c3nav.mapdata.models.locations import Location, LocationGroup
from c3nav.mapdata.models.section import Section
from c3nav.routing.connection import GraphConnection from c3nav.routing.connection import GraphConnection
from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable from c3nav.routing.exceptions import AlreadyThere, NoRouteFound, NotYetRoutable
from c3nav.routing.level import GraphLevel from c3nav.routing.level import GraphLevel
@ -28,7 +28,7 @@ class Graph:
def __init__(self, mtime=None): def __init__(self, mtime=None):
self.mtime = mtime self.mtime = mtime
self.levels = OrderedDict() self.levels = OrderedDict()
for level in Section.objects.all(): for level in Level.objects.all():
self.levels[level.name] = GraphLevel(self, level) self.levels[level.name] = GraphLevel(self, level)
self.points = [] self.points = []

View file

@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from c3nav.mapdata.models.section import Section from c3nav.mapdata.models.level import Level
ctype_mapping = { ctype_mapping = {
'yes': ('up', 'down'), 'yes': ('up', 'down'),
@ -232,7 +232,7 @@ def main(request, location=None, origin=None, destination=None):
def map_image(request, area, level): def map_image(request, area, level):
level = get_object_or_404(Section, name=level, intermediate=False) level = get_object_or_404(Level, name=level, intermediate=False)
if area == ':base': if area == ':base':
img = get_render_path('png', level.name, 'full', True) img = get_render_path('png', level.name, 'full', True)
elif area == ':full': elif area == ':full':