diff --git a/src/c3nav/mapdata/models/geometry/level.py b/src/c3nav/mapdata/models/geometry/level.py
index 3b52a5e7..1cd674a2 100644
--- a/src/c3nav/mapdata/models/geometry/level.py
+++ b/src/c3nav/mapdata/models/geometry/level.py
@@ -64,7 +64,7 @@ class LevelGeometryMixin(GeometryMixin):
_('Level'),
{
'id': self.level_id,
- 'slug': self.level.get_slug(),
+ 'slug': self.level.effective_slug,
'title': self.level.title,
'can_search': self.level.can_search,
},
diff --git a/src/c3nav/mapdata/models/geometry/space.py b/src/c3nav/mapdata/models/geometry/space.py
index e2747cbb..7f9ed6ae 100644
--- a/src/c3nav/mapdata/models/geometry/space.py
+++ b/src/c3nav/mapdata/models/geometry/space.py
@@ -93,7 +93,7 @@ class SpaceGeometryMixin(GeometryMixin):
_('Space'),
{
'id': self.space_id,
- 'slug': self.space.get_slug(),
+ 'slug': self.space.effective_slug,
'title': self.space.title,
'can_search': self.space.can_search,
},
diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py
index 5c133d87..dd20a88c 100644
--- a/src/c3nav/mapdata/models/locations.py
+++ b/src/c3nav/mapdata/models/locations.py
@@ -81,18 +81,20 @@ class LocationSlug(SerializableMixin, models.Model):
return getattr(self, model._meta.default_related_name)
return None
- def get_slug(self):
+ @property
+ def effective_slug(self):
return self.slug
def _serialize(self, **kwargs):
result = super()._serialize(**kwargs)
result["locationtype"] = self.__class__.__name__.lower()
- result['slug'] = self.get_slug()
+ result['slug'] = self.slug
+ result['effective_slug'] = self.effective_slug
return result
def details_display(self, **kwargs):
result = super().details_display(**kwargs)
- result['display'].insert(2, (_('Slug'), self.get_slug()))
+ result['display'].insert(2, (_('Slug'), self.effective_slug))
return result
@cached_property
@@ -116,9 +118,9 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
def serialize(self, detailed=True, **kwargs):
result = super().serialize(detailed=detailed, **kwargs)
if not detailed:
- fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square',
- 'locations', 'on_top_of', 'effective_label_settings', 'label_override', 'add_search', 'dynamic',
- 'locationtype', 'geometry')
+ fields = ('id', 'type', 'slug', 'effective_slug', 'title', 'subtitle', 'icon', 'point', 'bounds',
+ 'grid_square', 'locations', 'on_top_of', 'effective_label_settings', 'label_override',
+ 'add_search', 'dynamic', 'locationtype', 'geometry')
result = {name: result[name] for name in fields if name in result}
return result
@@ -144,7 +146,8 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
])
return result
- def get_slug(self):
+ @property
+ def effective_slug(self):
if self.slug is None:
code = self.LOCATION_TYPE_CODES.get(self.__class__.__name__)
if code is not None:
@@ -244,7 +247,7 @@ class SpecificLocation(Location, models.Model):
category.title if category.single else category.title_plural,
tuple({
'id': group.pk,
- 'slug': group.get_slug(),
+ 'slug': group.effective_slug,
'title': group.title,
'can_search': group.can_search,
} for group in sorted(groups, key=attrgetter('priority'), reverse=True))
@@ -356,6 +359,8 @@ class LocationGroupManager(models.Manager):
class LocationGroup(Location, models.Model):
+ new_serialize = True
+
class CanReportMissing(models.TextChoices):
DONT_OFFER = "dont_offer", _("don't offer")
REJECT = "reject", _("offer in first step, then reject")
@@ -692,7 +697,8 @@ class Position(CustomLocationProxyMixin, models.Model):
def slug(self):
return 'p:%s' % self.secret
- def get_slug(self):
+ @property
+ def effective_slug(self):
return self.slug
def serialize(self, *args, **kwargs):
diff --git a/src/c3nav/mapdata/schemas/model_base.py b/src/c3nav/mapdata/schemas/model_base.py
index 568d4b6b..f01976bc 100644
--- a/src/c3nav/mapdata/schemas/model_base.py
+++ b/src/c3nav/mapdata/schemas/model_base.py
@@ -41,11 +41,15 @@ class DjangoModelSchema(BaseSchema):
class LocationSlugSchema(BaseSchema):
- slug: NonEmptyStr = APIField(
+ slug: Optional[NonEmptyStr] = APIField(
title="location slug",
description="a slug is a unique way to refer to a location. while locations have a shared ID space, slugs"
- "are meants to be human-readable and easy to remember.\n\n"
- "if a location doesn't have a slug defined, this field holds a slug generated from the "
+ "are meants to be human-readable and easy to remember.",
+ example="entrance",
+ )
+ effective_slug: NonEmptyStr = APIField(
+ title="effective location slug",
+ description="if a location doesn't have a slug defined, this field holds a slug generated from the "
"location type and ID, which will work even if a human-readable slug is defined later.\n\n"
"even dynamic locations like coordinates have an (auto-generated) slug.",
example="entrance",
diff --git a/src/c3nav/mapdata/schemas/models.py b/src/c3nav/mapdata/schemas/models.py
index 3a19bbb9..13b83cb4 100644
--- a/src/c3nav/mapdata/schemas/models.py
+++ b/src/c3nav/mapdata/schemas/models.py
@@ -222,16 +222,7 @@ class LocationGroupSchema(LocationSchema, DjangoModelSchema):
)
priority: int = APIField() # todo: ???
hierarchy: int = APIField() # todo: ???
- label_settings: Union[
- Annotated[LabelSettingsSchema, APIField(
- title="label settings",
- description="label settings to use for gruop members that don't have their own set",
- )],
- Annotated[None, APIField(
- title="null",
- description="no label settings set"
- )],
- ] = APIField(
+ label_settings: Optional[PositiveInt] = APIField(
default=None,
title="label settings",
description=(
diff --git a/src/c3nav/mapdata/utils/cache/stats.py b/src/c3nav/mapdata/utils/cache/stats.py
index 6bf54c1f..f718639c 100644
--- a/src/c3nav/mapdata/utils/cache/stats.py
+++ b/src/c3nav/mapdata/utils/cache/stats.py
@@ -90,7 +90,7 @@ def convert_locate(data):
for measurement in BeaconMeasurement.objects.all().select_related('space', 'space__level'):
pos = CustomLocation(measurement.space.level, measurement.geometry.x, measurement.geometry.y,
permissions=set())
- space_slug = measurement.space.get_slug()
+ space_slug = measurement.space.effective_slug
level_label = measurement.space.level.short_label
grid_square = pos.grid_square if grid.enabled else None
measurement_lookup[pos.pk] = (measurement.pk, grid_square, space_slug, level_label)
@@ -143,23 +143,23 @@ def convert_location(data):
location = location.get_child()
if isinstance(location, LocationRedirect):
continue
- result['locations']['by_type'].setdefault(location.__class__.__name__.lower(), {})[location.get_slug()] = 0
- location_slugs[location.pk] = location.get_slug()
+ result['locations']['by_type'].setdefault(location.__class__.__name__.lower(), {})[location.effective_slug] = 0
+ location_slugs[location.pk] = location.effective_slug
if isinstance(location, Level):
result['locations']['by_level'][location.short_label] = 0
result['coordinates']['by_level'][location.short_label] = 0
level_labels[location.pk] = location.short_label
if isinstance(location, Space):
- result['locations']['by_space'][location.get_slug()] = 0
- result['coordinates']['by_space'][location.get_slug()] = 0
+ result['locations']['by_space'][location.effective_slug] = 0
+ result['coordinates']['by_space'][location.effective_slug] = 0
if isinstance(location, Area):
if getattr(location, 'can_search', False) or getattr(location, 'can_describe', False):
- result['coordinates']['by_area'][location.get_slug()] = 0
+ result['coordinates']['by_area'][location.effective_slug] = 0
if isinstance(location, POI):
if getattr(location, 'can_search', False) or getattr(location, 'can_describe', False):
- result['coordinates']['by_poi'][location.get_slug()] = 0
+ result['coordinates']['by_poi'][location.effective_slug] = 0
if isinstance(location, LocationGroup):
- result['locations']['by_group'][location.get_slug()] = 0
+ result['locations']['by_group'][location.effective_slug] = 0
for name, value in data:
if name[0] != 'pk' or name[0] == 'c:anywhere':
@@ -187,7 +187,7 @@ def convert_location(data):
result['locations']['total'] += value
location = getattr(location, 'target', location)
result['locations']['by_type'].setdefault(location.__class__.__name__.lower(),
- {})[location.get_slug()] += value
+ {})[location.effective_slug] += value
if hasattr(location, 'space_id'):
result['locations']['by_space'][location_slugs[location.space_id]] += value
if hasattr(location, 'level_id'):
diff --git a/src/c3nav/mapdata/utils/locations.py b/src/c3nav/mapdata/utils/locations.py
index 2e3a3ba0..451f313a 100644
--- a/src/c3nav/mapdata/utils/locations.py
+++ b/src/c3nav/mapdata/utils/locations.py
@@ -342,32 +342,32 @@ class CustomLocation:
(_('Slug'), self.pk),
(_('Level'), {
'id': self.level.pk,
- 'slug': self.level.get_slug(),
+ 'slug': self.level.effective_slug,
'title': self.level.title,
'can_search': self.level.can_search,
}),
(_('Space'), {
'id': self.space.pk,
- 'slug': self.space.get_slug(),
+ 'slug': self.space.effective_slug,
'title': self.space.title,
'can_search': self.space.can_search,
} if self.space else None),
(_('Areas'), tuple({
'id': area.pk,
- 'slug': area.get_slug(),
+ 'slug': area.effective_slug,
'title': area.title,
'can_search': area.can_search,
} for area in self.areas)),
(_('Grid Square'), self.grid_square or None),
(_('Near Area'), {
'id': self.near_area.pk,
- 'slug': self.near_area.get_slug(),
+ 'slug': self.near_area.effective_slug,
'title': self.near_area.title,
'can_search': self.near_area.can_search,
} if self.near_area else None),
(_('Near POI'), {
'id': self.near_poi.pk,
- 'slug': self.near_poi.get_slug(),
+ 'slug': self.near_poi.effective_slug,
'title': self.near_poi.title,
'can_search': self.near_poi.can_search,
} if self.near_poi else None),
@@ -459,7 +459,8 @@ class CustomLocation:
def get_icon(self):
return self.icon
- def get_slug(self):
+ @property
+ def effective_slug(self):
return self.pk
@cached_property
diff --git a/src/c3nav/mapdata/views.py b/src/c3nav/mapdata/views.py
index 389dcdca..2fe06914 100644
--- a/src/c3nav/mapdata/views.py
+++ b/src/c3nav/mapdata/views.py
@@ -150,7 +150,7 @@ def preview_location(request, slug):
if location is None:
raise Http404
- slug = location.get_slug()
+ slug = location.effective_slug
if isinstance(location, CustomLocation):
geometries = [Point(location.x, location.y)]
diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js
index 713eb7f5..d59fc445 100644
--- a/src/c3nav/site/static/site/js/c3nav.js
+++ b/src/c3nav/site/static/site/js/c3nav.js
@@ -169,7 +169,7 @@ c3nav = {
location.elem = c3nav._build_location_html(location);
location.title_words = location.title.toLowerCase().split(/\s+/);
location.subtitle_words = location.subtitle.toLowerCase().split(/\s+/);
- location.match = ' ' + location.title_words.join(' ') + ' ' + location.subtitle_words.join(' ') + ' ' + location.slug + ' ' + location.add_search.toLowerCase();
+ location.match = ' ' + location.title_words.join(' ') + ' ' + location.subtitle_words.join(' ') + ' ' + location.effective_slug + ' ' + location.add_search.toLowerCase();
locations.push(location);
locations_by_id[location.id] = location;
if (location.point && location.label_settings) {
@@ -464,7 +464,7 @@ c3nav = {
for (var j = 0; j < sublocations.length; j++) {
loc = sublocations[j];
if (loc.can_search) {
- loclist.append($('').attr('href', '/l/' + loc.slug + '/details/').attr('data-id', loc.id).click(function (e) {
+ loclist.append($('').attr('href', '/l/' + loc.effective_slug + '/details/').attr('data-id', loc.id).click(function (e) {
e.preventDefault();
c3nav._locationinput_set($('#destination-input'), c3nav.locations_by_id[parseInt($(this).attr('data-id'))]);
c3nav.update_state(false, false, true);
@@ -736,12 +736,12 @@ c3nav = {
var url = embed ? '/embed' : '';
if (state.routing) {
if (state.origin) {
- url += (state.destination) ? '/r/' + state.origin.slug + '/' + state.destination.slug + '/' : '/o/' + state.origin.slug + '/';
+ url += (state.destination) ? '/r/' + state.origin.effective_slug + '/' + state.destination.effective_slug + '/' : '/o/' + state.origin.effective_slug + '/';
} else {
- url += (state.destination) ? '/d/' + state.destination.slug + '/' : '/r/';
+ url += (state.destination) ? '/d/' + state.destination.effective_slug + '/' : '/r/';
}
} else {
- url += state.destination ? ('/l/' + state.destination.slug + '/') : '/';
+ url += state.destination ? ('/l/' + state.destination.effective_slug + '/') : '/';
}
if (state.details && (url.startsWith('/l/') || url.startsWith('/r/'))) {
url += 'details/'
@@ -900,9 +900,8 @@ c3nav = {
_buttons_share_click: function (location) {
var url = c3nav._get_share_url(false, location);
if (navigator.share) {
- var title
- , subtitle;
- if (location.slug) {
+ var title, subtitle;
+ if (location.effective_slug) {
title = location.title;
subtitle = location.subtitle;
} else {
@@ -923,8 +922,8 @@ c3nav = {
},
_get_share_url: function (with_position, location) {
var url, state = $.extend({}, c3nav.state);
- if (location.slug) {
- url = '/l/' + location.slug + '/';
+ if (location.effective_slug) {
+ url = '/l/' + location.effective_slug + '/';
} else {
if (!with_position) {
state.center = null;
diff --git a/src/c3nav/site/views.py b/src/c3nav/site/views.py
index fa414fd4..e715e11d 100644
--- a/src/c3nav/site/views.py
+++ b/src/c3nav/site/views.py
@@ -150,24 +150,24 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
metadata = {
'title': _('Route from %s to %s') % (origin.title, destination.title),
'preview_img_url': request.build_absolute_uri(reverse('mapdata.preview.route', kwargs={
- 'slug': origin.get_slug(),
- 'slug2': destination.get_slug(),
+ 'slug': origin.effective_slug,
+ 'slug2': destination.effective_slug,
})),
'canonical_url': request.build_absolute_uri(reverse('site.index', kwargs={
'mode': 'r',
- 'slug': origin.get_slug(),
- 'slug2': destination.get_slug(),
+ 'slug': origin.effective_slug,
+ 'slug2': destination.effective_slug,
'details': False,
'options': False,
})),
}
elif destination is not None or origin is not None:
if destination is not None:
- loc_slug = destination.get_slug()
+ loc_slug = destination.effective_slug
title = destination.title
subtitle = destination.subtitle if hasattr(destination, 'subtitle') else None
else:
- loc_slug = origin.get_slug()
+ loc_slug = origin.effective_slug
title = origin.title
subtitle = origin.subtitle if hasattr(origin, 'subtitle') else None
metadata = {
@@ -579,7 +579,7 @@ def report_missing_choose(request, coordinates):
'locations': [
{
"url": reverse('site.report_create',
- kwargs={"coordinates": coordinates, "group": group.get_slug()}),
+ kwargs={"coordinates": coordinates, "group": group.effective_slug}),
"location": group,
"replace_subtitle": group.description
}