【问题标题】:Sorting by distance with a related ManyToMany field使用相关的 ManyToMany 字段按距离排序
【发布时间】:2013-03-07 06:12:05
【问题描述】:

我有这两个模型。

class Store(models.Model):
    coords = models.PointField(null=True,blank=True)
    objects = models.GeoManager()

class Product(models.Model):
    stores  = models.ManyToManyField(Store, null=True, blank=True)
    objects = models.GeoManager()

我想按到某个点的距离对产品进行排序。如果 Product 中的 stores 字段是外键,我会这样做并且它可以工作。

pnt = GEOSGeometry('POINT(5 23)')
Product.objects.distance(pnt, field_name='stores__coords').order_by('distance')

但由于该字段是 ManyToMany 字段,因此它与

ValueError: <django.contrib.gis.db.models.fields.PointField: coords> is not in list

我有点预料到这一点,因为不清楚它应该使用哪个商店来计算距离,但有什么办法可以做到这一点。

我需要按到特定点的距离排序的产品列表。

【问题讨论】:

  • 我也遇到了这个困难。也许这对 GeoDjango 是不可能的?也许必须为它创建一个原始 sql?
  • @JoeJ 我将发布我所做的作为可能的答案,但我不喜欢它。可能原始 SQL 可以工作,但我对空间查询等不太满意。看看答案,看看你的想法。
  • 产品有ManyToManyStore,但拥有PointField 的是商店。一个产品可以在 1 个或多个商店... 对于一个拥有超过 1 个商店的产品来说,距离是多少? 越低?越高 ?全部?
  • @Liarez 好问题。就我而言,它是最接近的。我需要一个产品搜索,在地图上显示你周围的产品。

标签: django geodjango django-orm


【解决方案1】:

这就是我解决它的方法,但我不太喜欢这个解决方案。我认为是非常低效的。 GeoDjango 应该有更好的方法。所以,在我找到更好的解决方案之前,我可能不会使用它。这就是我所做的。

我在产品模型中添加了一个新方法

class Product(models.Model):
    stores  = models.ManyToManyField(Store, null=True, blank=True)
    objects = models.GeoManager()

    def get_closes_store_distance(point):
        sorted_stores =  self.stores.distance(point).order_by('distance')
        if sorted_stores.count() > 0:
            store = sorted_stores[0]
            return store.distance.m
        return 99999999 # If no store, return very high distance

那我可以这样排序

def sort_products(self, obj_list, lat, lng):
    pt = 'POINT(%s %s)' % (lng, lat)
    srtd = sorted(obj_list, key=lambda obj: obj.get_closest_store_distance(pt))
    return srtd

非常欢迎任何更好的解决方案或改进方法。

【讨论】:

  • 您是否碰巧找到了有效的解决方案?
  • @VikasGulati & manuel:我将重复对这个问题的评论(VikasGulati 可能没有看到):什么是“从产品到点的距离”? (可能是从该点到该产品的商店的最小距离。)输出究竟是什么? (可能是按距离升序排序的所有产品的(产品、距离)列表。)
  • @philipxy:是的,距离是最小的。我希望在类似问题中实现的输出是按计算的最小距离排序的产品列表 + 每个产品都应该预先获取相关商店,并再次按距离排序。
【解决方案2】:

只是一个想法,也许这对你有用,这应该只需要两个数据库查询(由于预取的工作原理)。行不通就别苛责了,我没试过:

class Store(models.Model):
    coords = models.PointField(null=True,blank=True)
    objects = models.GeoManager()

class Product(models.Model):
    stores  = models.ManyToManyField(Store, null=True, blank=True, through='ProductStore')
    objects = models.GeoManager()

class ProductStore(models.Model):
    product = models.ForeignKey(Product)
    store = models.ForeignKey(Store)
    objects = models.GeoManager()

然后:

pnt = GEOSGeometry('POINT(5 23)')
ps = ProductStore.objects.distance(pnt, field_name='store__coords').order_by('distance').prefetch_related('product')
for p in ps:
    p.product ... # do whatever you need with it

【讨论】:

  • 感谢您的回答。我在一年前发布了这个,不幸的是我无法访问代码或测试它的方法了。如果有人发现它对他们有用,我会将其标记为已回答。
【解决方案3】:

我将“从产品到点的距离”作为从点到具有该产品的商店的最小距离。我将输出作为按距离升序排序的所有产品的(产品、距离)列表。 (放置赏金的人的评论表明,他们有时还希望(产品、距离、商店)按距离排序,然后存储在产品中。)

每个模型都有一个对应的表格。模型的字段是表的列。每个模型/表格都应该有一个填充(命名)空白语句,其中它的记录/行是做出真实陈述的那些。

Store(coords,...) // store [store] is at [coords] and ...
Product(product,store,...) // product [product] is stocked by store [store] and ...

由于 Product 的 store(s) as manyToManyField 它已经是产品和库存商店的“ProductStore”表,而 Store 已经是商店及其坐标的“StoreCoord”表。

对于具有 manyToManyField 的模型,您可以在查询 filter() 中提及任何对象的字段。

这方面的 SQL 很简单:

select p.product,distance
    select p.product,distance(s.coord,[pnt]) as distance
    from Store s join Product p
    on s.store=p.store
group by product
having distance=min(distance)
order by distance

将其映射到query 应该很简单。但是,我对 Django 还不够熟悉,现在无法给你确切的代码。

from django.db.models import F

q = Product.objects.all()
    .filter(store__product=F('product'))
    ...
    .annotate(distance=Min('coord.distance([pnt])'))
    ...
    .order_by('distance')

Min() 是aggregation 的一个示例。

您也可以通过明确的subquery 获得帮助。

也可以通过raw接口查询。但是,上面的名称不适用于 Django 原始查询。例如,表名默认为 APPL_store 和 APPL_product,其中 APPL 是您的应用程序名称。此外,距离不是您的 pointField 运算符。你必须给出正确的距离函数。但是您不需要在原始级别进行查询。

【讨论】:

  • 感谢您的回答。我在一年前发布了这个,不幸的是我无法访问代码或测试它的方法了。如果有人发现它对他们有用,我会将其标记为已回答。
  • 明白。 (Vikas Gulati最近在上面发布了一个赏金。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-02
相关资源
最近更新 更多