Django’s GeoDjango library is excellent, but for a new project I started working on, I just needed to output existing data as GeoJSON, which could then be used with Mapbox. The problem is, that using GeoDjango library would require a lot of additional setup- I don't need PostGis, or geographic objects right now; I just need data output as JSON.
I’ve already been using Django Rest Framework, so I have an API set up for the rest of the site. I just needed an endpoint to return GeoJSON. The great thing about DRF is the ability to create custom serializers. After a little trial and error, I was able to create an endpoint that returns data in a standard GeoJSON format! So with our further ado:
My model:
A pretty basic model. The key field here is a JSONfield to store longitude and latitude.
class Event(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=250, blank=True, null=True)
description = models.CharField(
blank=True,
null=True,
max_length=512,
help_text="Reading a book, watching a movie, etc",
)
content = models.TextField(blank=True, null=True)
content_html = models.TextField(editable=False, blank=True, null=True)
pub_date = models.DateTimeField(
verbose_name="When the event happened, or is going to happen.", blank=True, null=True
)
date_created = models.DateTimeField(auto_now_add=True, editable=False)
date_updated = models.DateTimeField(auto_now=True, editable=False)
is_public = models.BooleanField(help_text="Should be checked to show up on global timelines.", default=False)
is_active = models.BooleanField(help_text="Is it published?", default=False)
location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL)
lnglat = models.JSONField(blank=True, null=True)
The view:
I love using viewsets. They keep everything organized, especially when you’re using a lot of different endpoints and methods. Here I'm doing two endpoints; retrieving a list of the latest public events, and a retrieve to get any given specific event.
class EventViewSet(ViewSet):
"""Events"""
permission_classes = [OwnsObjectOrReadOnly]
def list(self, request):
"""Get all public events"""
events = Event.objects.public()
serialized_data = GeoJsonEventSerializer(events, many=True, context={"request": request})
return Response(
{
"result": serialized_data.data,
}
)
def retrieve(self, request, pk=None):
"""Get an event"""
if request.user.is_authenticated:
event = get_object_or_404(Event, pk=pk)
else:
event = get_object_or_404(
Event,
pk=pk,
is_active=True,
is_public=True,
)
serialized_data = GeoJsonEventSerializer(event, context={"request": request})
return Response(
{
"result": serialized_data.data,
}
)
The serializer:
The key is to format the JSON structure in a very specific way. Using to_representation
lets us achieve that.
class GeoJsonEventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = [
"id",
"user",
"name",
"description",
"content_html",
"pub_date",
"date_updated",
"date_created",
"lnglat",
"location",
"timeline",
"image",
]
def to_representation(self, instance):
# instance is the model object. create the custom json format by accessing instance attributes normaly and return it
identifiers = dict()
identifiers["name"] = instance.name
identifiers["description"] = instance.description
representation = {
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [instance.lnglat.get("Lng"), instance.lnglat.get("Lat")]},
"properties": {
"name": instance.name,
"description": instance.description,
"url": "http://fubar/",
},
}
return representation
That's it! My API is now returning GeoJSON objects that I can then load into Mapbox. Of course, this is just the start. Eventually, I very well replace a lot of this with the GeoDjango framework, but for now, this was a quick simple hack of getting Django Rest Framework to return exactly what I need to prototype a project.