使用相关的ManyToMany字段按距离排序

时间:2013-03-18 19:01:47

标签: django geodjango django-orm

我有这两种模式。

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

我有点期待这个,因为它不清楚它应该用哪个商店来计算距离,但有没有办法做到这一点。

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

3 个答案:

答案 0 :(得分:1)

只是一个想法,也许这对你有用,这应该只需要两个数据库查询(由于预取工作原理)。如果它不起作用,不要严厉地判断,我没有尝试过:

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

答案 1 :(得分:0)

这就是我解决它的方法,但我真的不喜欢这个解决方案。我觉得效率很低。 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

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

答案 2 :(得分:0)

我将采取从产品到点的距离&#34;从该点到该产品的商店的最小距离。我将把输出作为按距离递增排序的所有产品的(产品,距离)列表。 (放置赏金的人的评论表明他们有时也希望(产品,距离,商店)按距离排序,然后存储在产品中。)

每个模型都有一个相应的表格。模型的字段是表的列。每个模型/表都应该有一个填充(命名)空白语句,其中的记录/行是真正的语句。

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

由于Product已经存储了很多ToManyField,因此它已经是&#34; ProductStore&#34;产品和库存商店的表和商店已经是&#34; StoreCoord&#34;商店表及其坐标。

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

这方面的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运算符。你必须给出正确的距离功能。但是你不需要在原始级别进行查询。

相关问题