why doesn't Perimeter work with polygons?

I’ll be talking about django.contrib.gis.db.models.functions.Perimeter and the as_postgresql method

First case with GeneratedField

from django.contrib.gis.db.models.functions import GeoFunc
from django.db import models
from django.contrib.gis.db.models import PolygonField


class Geography(GeoFunc):
    function = 'geography'


class Crosswalk(models.Model):
    polygon = PolygonField()
    perimeter = models.GeneratedField(
        expression=Perimeter(Geography('polygon')),
        output_field=models.FloatField(),
        db_persist=True,
    )

When i try to migrate i get this error

django.db.utils.NotSupportedError: ST_Perimeter cannot use a non-projected non-geography field.

Second case with annotate and without Geography

from django.db import models
from django.contrib.gis.db.models import PolygonField


class Crosswalk(models.Model):
    polygon = PolygonField()


Crosswalk.objects.annotate(p=Perimeter('polygon'))

And on this case i too get this error

django.db.utils.NotSupportedError: ST_Perimeter cannot use a non-projected non-geography field.

But if I make a custom Perimeter function and remove this check, everything works correctly and I get the result.

class P(Perimeter):
    function = 'st_perimeter'

    def as_postgresql(self, compiler, connection, **extra_context):
        function = None
        dim = min(f.dim for f in self.get_source_fields())
        if dim > 2:
            function = connection.ops.perimeter3d
        return super().as_sql(compiler, connection, function=function, **extra_context)

Source: django/django/contrib/gis/db/models/functions.py at main · django/django · GitHub

Why does it work that way? Is it a bug?

Did you check the result of your Perimeter query? If I’m not mistaken, the result should be in degrees, which is not what is expected.

As per your first question, it is totally possible that Django is not able to determine that the expression final output is in fact geography. The check is probably too naive and could probably receive some love to make it smarter.

You’re really right, I apologize. But it’s not really obvious that you can’t find a perimeter with the perimeter function.