2013-03-18 13 views
8

Ich habe diese zwei Modelle.Sortierung nach Entfernung mit einem verwandten ManyToMany Feld

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() 

Ich möchte die Produkte sortiert nach der Entfernung zu einem Punkt. Wenn das Speicherfeld im Produkt ein Fremdschlüssel wäre, würde ich dies tun und es funktioniert.

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

Da aber das Feld ist ein ManyToMany Feld mit

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

bricht Ich Art dies zu erwarten, weil es die von den Geschäften nicht klar ist, sollte es den Abstand berechnen zu verwenden, aber gibt es ein Weg, dies zu tun.

Ich brauche die Liste der Produkte sortiert nach Entfernung zu einem bestimmten Punkt.

+0

Ich hatte diese Schwierigkeit auch. Vielleicht ist das mit GeoDjango nicht möglich? Vielleicht muss man dafür einen rohen sql erstellen? –

+0

@JoeJ Ich poste, was ich als mögliche Antwort getan habe, aber ich mag es nicht. Wahrscheinlich kann das rohe SQL funktionieren, aber ich bin nicht sehr komfortabel mit räumlichen Abfragen und dem. Überprüfen Sie die Antwort und sehen Sie, was Sie denken. – manuel

+4

Das Produkt hat ein 'ManyToMany' zum' Store', aber es ist das Geschäft, das 'PointField' hat. Ein Produkt kann in 1 oder mehr Geschäften sein ... ** Wich ist die Entfernung für ein Produkt, das mehr als 1 Geschäft hat? ** Das niedrigere? Der höhere ? Alle ? – AlvaroAV

Antwort

0

So habe ich es gelöst, aber ich mag diese Lösung nicht wirklich. Ich denke, ist sehr ineffizient. Es sollte einen besseren Weg mit GeoDjango geben. Also, bis ich eine bessere Lösung finden werde ich wahrscheinlich nicht verwenden. Hier ist was ich getan habe.

habe ich eine neue Methode zum Produktmodell

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 

Dann kann ich

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 

besser Lösungen oder Möglichkeiten, diese Art und Weise sortieren, diese zu verbessern sind sehr willkommen.

+1

Haben Sie zufällig eine effiziente Lösung gefunden? –

+0

@VikasGulati & manuel: Ich wiederhole einen Kommentar zu der Frage (die VikasGulati vielleicht nicht gesehen hat): Was ist "Abstand von einem Produkt zu einem Punkt"? (Wahrscheinlich der Mindestabstand von dem Punkt zu einem Geschäft mit diesem Produkt.) Was ist die Ausgabe genau? (Wahrscheinlich eine Liste von (Produkt, Entfernung) für alle Produkte nach Entfernung aufsteigend sortiert.) – philipxy

+0

@philipxy: Ja ist der Abstand der Mindestabstand. Und die Ausgabe, die ich bei einem ähnlichen Problem erreichen wollte, war eine Liste von Produkten, die nach der berechneten Mindestdistanz sortiert war. Jedes Produkt sollte die zuvor abgerufenen zugehörigen Filialen nach Entfernung sortiert haben. –

1

Nur eine Idee, vielleicht würde dies für Sie funktionieren, sollte dies nur zwei Datenbankabfragen (aufgrund der Vorabruf funktioniert). Richte nicht hart, wenn es nicht funktioniert, ich habe es nicht versucht:

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() 

dann:

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 
+0

Danke für Ihre Antwort. Ich habe dies vor über einem Jahr gepostet und leider habe ich keinen Zugriff auf den Code oder eine Möglichkeit, dies zu testen. Wenn jemand findet, dass es für sie funktioniert, werde ich es als beantwortet markieren. – manuel

0

Ich werde „Abstand von einem Produkt zu einem Punkt“ zu sein, die Mindestabstand von dem Punkt zu einem Geschäft mit diesem Produkt. Ich nehme die Ausgabe, um eine Liste von (Produkt, Entfernung) für alle Produkte zu sein, sortiert nach Entfernung aufsteigend. (Ein Kommentar von jemandem, der eine Prämie platziert hat angegeben, dass sie manchmal auch wollen (Produkt, Entfernung, Geschäft) sortiert nach Entfernung dann im Produkt speichern.)

Jedes Modell hat eine entsprechende Tabelle. Die Felder des Modells sind die Spalten der Tabelle. Jedes Modell/jede Tabelle sollte eine fill-in-the- (named-) Leerzeichen-Anweisung haben, deren Datensätze/Zeilen diejenigen sind, die eine wahre Aussage abgeben.

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

Seit Produkt hat Shop (s) als ManyToManyField es bereits eine „ProductStor“ Tabelle von Produkten und Lagerhäuser und Speicher ist bereits ein „StoreCoord“ -Tabelle von Geschäften und deren Koordinaten.

Sie können die Felder eines beliebigen Objekts in einem Abfragefilter() für ein Modell mit einem manyToManyField angeben.

Die SQL dafür ist einfach:

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 

Es sollte dies einfach sein kartieren zu einem query. Allerdings bin ich mit Django nicht vertraut genug, um dir jetzt genauen Code zu geben.

from django.db.models import F 

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

Die Min() ist ein Beispiel für aggregation.

Ihnen kann auch geholfen werden, indem Sie explizit eine subquery machen.

Es ist auch möglich, dies über die Schnittstelle raw abzufragen. Die obigen Namen sind jedoch nicht für eine Django-Rohabfrage geeignet. ZB sind die Tabellennamen standardmäßig APPL_store und APPL_product, wobei APPL der Name Ihrer Anwendung ist. Außerdem ist die Entfernung nicht Ihr PointField-Operator. Sie müssen die richtige Entfernungsfunktion angeben. Aber Sie sollten nicht auf der rohen Ebene abfragen müssen.

+0

Danke für Ihre Antwort. Ich habe dies vor über einem Jahr gepostet und leider habe ich keinen Zugriff auf den Code oder eine Möglichkeit, dies zu testen. Wenn jemand findet, dass es für sie funktioniert, werde ich es als beantwortet markieren. – manuel

+0

Verstehen. (Ein Kopfgeld wurde kürzlich von [Vikas Gulati] gepostet (https://stackoverflow.com/users/1283546/vikas-gulati).) – philipxy

Verwandte Themen