Properties¶
To run this example, we’ll need to install django-enum with property support:
> pip install "django-enum[properties]"
MapBox Styles¶
MapBox is a leading web mapping platform. It comes with a handful of default
map styles. An enumeration is a
natural choice to represent these styles but the styles are complicated by versioning and are
identified by different properties depending on context. When used as a parameter in the MapBox API
they are in URI format, but in our interface we would prefer a more human friendly label, and in
code we prefer the brevity and reliability of an Enum
value attribute.
Each MapBox style enumeration is therefore composed of 4 primary properties:
A a human friendly label for the style
A name slug used in the URI
A version number for the style
The full URI specification of the style.
Leveraging IntEnumProperties
We might implement our style enumeration like
so:
import typing as t
from django.db import models
from enum_properties import symmetric, Symmetric, IntEnumProperties
from django_enum import EnumField
class Map(models.Model):
class MapBoxStyle(IntEnumProperties):
"""
https://docs.mapbox.com/api/maps/styles/
"""
label: t.Annotated[str, Symmetric(case_fold=True)]
slug: t.Annotated[str, Symmetric(case_fold=True)]
version: int
# fmt: off
# name value label slug version
STREETS = 1, "Streets", "streets", 12
OUTDOORS = 2, "Outdoors", "outdoors", 12
LIGHT = 3, "Light", "light", 11
DARK = 4, "Dark", "dark", 11
SATELLITE = 5, "Satellite", "satellite", 9
SATELLITE_STREETS = 6, "Satellite Streets", "satellite-streets", 12
NAVIGATION_DAY = 7, "Navigation Day", "navigation-day", 1
NAVIGATION_NIGHT = 8, "Navigation Night", "navigation-night", 1
# fmt: on
@symmetric()
@property
def uri(self):
return f"mapbox://styles/mapbox/{self.slug}-v{self.version}"
def __str__(self):
return self.uri
style = EnumField(MapBoxStyle, default=MapBoxStyle.STREETS)
We’ve used a small integer as the value of the enumeration to save storage space. We’ve also added a symmetric case insensitive slug and a version property.
The version numbers will increment over time, but we’re only concerned with the most recent
versions, so we’ll increment their values in this enumeration as they change. Any version number
updates exist only in code and will be picked up as those persisted values are re-instantiated as
MapBoxStyle
enumerations.
The last property we’ve added is the uri
property. We’ve added it as concrete property on the
class because it can be created from the slug and version. We could have specified it in the value
tuple but that would be very verbose and less
DRY. To make this property symmetric we
decorated it with symmetric()
.
We can use our enumeration like so:
map = Map.objects.create()
assert map.style.uri == 'mapbox://styles/mapbox/streets-v12'
# uri's are symmetric
map.style = 'mapbox://styles/mapbox/light-v11'
map.full_clean()
assert map.style is Map.MapBoxStyle.LIGHT
# comparisons can be made directly to symmetric property values
assert map.style == 3
assert map.style == 'light'
assert map.style == 'mapbox://styles/mapbox/light-v11'
# so are labels (also case insensitive)
map.style = 'satellite streets'
map.full_clean()
assert map.style is Map.MapBoxStyle.SATELLITE_STREETS
# when used in API calls (coerced to strings) - they "do the right thing"
assert str(map.style) == 'mapbox://styles/mapbox/satellite-streets-v12'