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.