openwisp / django-rest-framework-gis

Geographic add-ons for Django REST Framework. Maintained by the OpenWISP Project.
http://openwisp.org
MIT License
1.07k stars 200 forks source link

Idea of support for RawQuery using the classical class based flow of Django and DRF #281

Open jlandercy opened 1 year ago

jlandercy commented 1 year ago

When dealing with GeoDjango the need for RawQuery is important as PostGIS queries are inherently difficult to write with the classical Django Queryset pattern.

When trying to bind everything together I have found a simple way to do it, and I share the recipe here because I might not be the only one having this need and there is no obvious documentation about it. At least this issue can last as a documentation.

But I also think it is a good use case to extend this package (which is very convenient by the way). I have the feeling it might be easy to draw a pull request from here. See my stack overflow request for peer reviewing for more details.

With the current version, the best way I have found to perform it, is:

class Location(Model):
    geom = models.PointField()

class CircleSerializer(GeoFeatureModelSerializer):

    id = serializers.IntegerField(required=True)
    circle = GeometrySerializerMethodField()

    def get_circle(self, obj):
        return GEOSGeometry(obj.circle)

    class Meta:
        model = Location
        geo_field = "circle"
        fields = ('id',)

class CircleViewSet(ListAPIView):

    queryset = Location.objects.raw("""
    SELECT id, ST_Buffer(L.geom::Geography, 800)::Geometry AS circle
    FROM location AS L;
    """)
    serializer_class = CircleSerializer

And it works as expected, and it is clean and concise.

But maybe something cleaver can be done to withdraw the need of explicitly cast the GIS column which in this scenario is returned as a str holding the geometry binary representation. Passing it to the GEOS geometry constructor solve the problem.

Maybe creating the field object, it can detect if the QuerySet is actually a RawQuery and add the extra GEOS casting on the fly. That would makes the code even simpler. Something like:

class Location(Model):
    geom = models.PointField()

class CircleSerializer(GeoFeatureModelSerializer):

    id = serializers.IntegerField(required=True)
    circle = GeometrySerializerField()       # <-- Some magic that perhaps could land here
    #circle = RawGeometrySerializerField()   # <-- Or into a new object

    class Meta:
        model = Location
        geo_field = "circle"
        fields = ('id',)

class CircleViewSet(ListAPIView):

    queryset = Location.objects.raw("""
    SELECT id, ST_Buffer(L.geom::Geography, 800)::Geometry AS circle
    FROM location AS L;
    """)
    serializer_class = CircleSerializer

Indeed it must not break the flow for other process. Another kind of isolation could be a totally new field object.

I open the discussion, let me know if it makes sense.

Best regards,

Jean

auvipy commented 1 year ago

what about using the query as django model manager?