8

我有这两个模型。

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

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

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

4

3 回答 3

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
于 2014-06-18T22:12:17.130 回答
0

I will take "distance from a product to a point" to be the minimum distance from the point to a store with that product. I will take the output to be a list of (product, distance) for all products sorted by distance ascending. (A comment by someone who placed a bounty indicated they sometimes also want (product,distance,store) sorted by distance then store within product.)

Every model has a corresponding table. The fields of the model are the columns of the table. Every model/table should have a fill-in-the-(named-)blanks statement where its records/rows are the ones that make a true statement.

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

Since Product has store(s) as manyToManyField it already is a "ProductStore" table of products and stocking stores and Store already is a "StoreCoord" table of stores and their coordinates.

You can mention any object's fields in a query filter() for a model with a manyToManyField.

The SQL for this is simple:

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

It should be straightforward to map this to a query. However, I am not familiar enough with Django to give you exact code now.

from django.db.models import F

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

The Min() is an example of aggregation.

You may also be helped by explicitly making a subquery.

It is also possible to query this by the raw interface. However, the names above are not right for a Django raw query. Eg the table names will by default be APPL_store and APPL_product where APPL is your application name. Also, distance is not your pointField operator. You must give the right distance function. But you should not need to query at the raw level.

于 2014-06-25T18:56:43.927 回答
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

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

于 2013-04-03T11:02:06.780 回答