在外键上为 Geodjango 自定义 SQL

Custom SQL for Geodjango on ForignKey

我有以下型号:

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    location = models.PointField(blank=True, null=True, srid=CONSTANTS.SRID)

    objects = models.GeoManager()

class Item(models.Model):
    owner = models.ForeignKey(UserProfile)

    objects = models.GeoManager()

现在我需要按到某个点的距离对项目进行排序:

p = Point(12.5807203, 50.1250706)
Item.objects.all().distance(p, field='owner__location') 

但这会给我一个错误:

TypeError: ST_Distance output only available on GeometryFields.

来自 GeoDjango GeoQuerySet.distance() results in 'ST_Distance output only available on GeometryFields' when specifying a reverse relationship in field_name 我可以看到已经有票了。

现在我不喜欢那个问题中提出的解决方案,因为那样我就不会得到距离,而且我会失去距离。

所以我想我可以通过自定义 sql 查询来实现这一点。我知道这个:

UserProfile.objects.distance(p)

会产生这样的结果:

SELECT (ST_distance_sphere("core_userprofile"."location",ST_GeomFromEWKB('\x0101000020e6100000223fd12b5429294076583c5002104940'::bytea))) AS "distance", "core_userprofile"."id", "core_userprofile"."user_id", "core_userprofile"."verified", "core_userprofile"."avatar_custom", "core_userprofile"."city", "core_userprofile"."location", "core_userprofile"."bio" FROM "core_userprofile"

所以我的问题是:是否有一些简单的方法可以手动构建这样的查询以按距离对项目进行排序?

由于您要测量距离的几何体位于 UserProfile 上,因此查询 UserProfile 对象然后处理它们拥有的每个 Item 对象是有意义的。 (距离对于配置文件拥有的所有项目都是相同的。)

例如:

all_profiles = UserProfile.objects.all()
for profile in all_profiles.distance(p).order_by('distance'):
   for item in profile.item_set.all():
       process(item, profile.distance)

您可以使用 prefetch_related 提高效率:

all_profiles = UserProfile.objects.all()
all_profiles = all_profiles.prefetch_related('item_set')  # we'll need these
for profile in all_profiles.distance(p).order_by('distance'):
   for item in profile.item_set.all():  # items already prefetched
       process(item, profile.distance)

如果出于某种原因直接查询 Item 对象很重要,请尝试使用 extra:

items = Item.objects.all()
items = items.select_related('owner')
distance_select = "st_distance_sphere(core_userprofile.location, ST_GeomFromEWKT('%s'))" % p.wkt
items = items.extra({'distance': distance_select})
items = items.order_by('distance')

Raw queries 是另一种选择,它允许您从原始 SQL 查询中获取模型对象:

items = Item.objects.raw("SELECT core_item.* FROM core_item JOIN core_userprofile ...")