2013-03-18 69 views
8

Mam dwa modele.Sortowanie według odległości z powiązanym polem ManyToMany

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

Chcę, aby produkty były sortowane według odległości do punktu. Jeśli sklepy w produkcie byłyby kluczem zagranicznym, zrobiłbym to i to działa.

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

Ale ponieważ jest polem ManyToMany zrywa z

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

I niby spodziewałem się tego, ponieważ nie jest jasne, które z magazynów należy użyć, aby obliczyć odległość, ale jest jakiś sposób, aby to zrobić.

Potrzebuję listę produktów zamówionych według odległości do określonego punktu.

+0

Mam również tę trudność. Może to nie jest możliwe z GeoDjango? Być może trzeba utworzyć dla niego raw sql? –

+0

@JoeJ Opublikuję to, co zrobiłem jako możliwą odpowiedź, ale nie podoba mi się to. Prawdopodobnie surowy SQL może działać, ale nie jestem zbyt komfortowy z zapytaniami przestrzennymi i tym. Sprawdź odpowiedź i zobacz, co myślisz. – manuel

+4

Produkt ma 'ManyToany' na' Store', ale jest to sklep, który ma 'PointField'. Produkt może znajdować się w co najmniej 1 sklepie ... ** Ile wynosi odległość produktu, który ma więcej niż jeden sklep? ** Czym jest niższy? Im wyżej? Wszystko ? – AlvaroAV

Odpowiedz

0

Tak to rozwiązałem, ale nie podoba mi się to rozwiązanie. Myślę, że jest bardzo nieefektywna. Powinien być lepszy sposób z GeoDjango. Tak więc, dopóki nie znajdę lepszego rozwiązania, prawdopodobnie nie będę tego używał. Oto co zrobiłem.

Dodałem nową metodę modelu produktu

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 

Wtedy mogę rozwiązać ten sposób

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 

Wszelkie lepszych rozwiązań i sposobów, aby poprawić ten są bardzo mile widziane.

+1

Czy znalazłeś skuteczne rozwiązanie? –

+0

@VikasGulati & manuel: Powtórzę komentarz do pytania (którego VikasGulati może nie widział): Czym jest "odległość od produktu do punktu"? (Prawdopodobnie minimalna odległość od punktu do sklepu z tym produktem.) Czym dokładnie jest wyjście? (Prawdopodobnie lista (produktu, odległości) dla wszystkich produktów posortowanych według odległości rosnąco.) – philipxy

+0

@philipxy: Tak odległość jest minimalna. I wyjście, które chciałbym osiągnąć w podobnym problemie, to lista produktów posortowana według obliczonej minimalnej odległości + Każdy produkt powinien mieć uprzednio pobrane powiązane magazyny posortowane według odległości. –

1

Po prostu pomysł, może to będzie działało dla ciebie, to powinno zająć tylko dwa zapytania do bazy danych (ze względu na działanie prefetch). Nie oceniać surowo, jeśli to nie działa, nie próbowałem go:

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

następnie:

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

Dzięki za odpowiedź. Wysłałem to ponad rok temu i niestety nie mam już dostępu do kodu ani sposobu, aby to przetestować. Jeśli ktoś stwierdzi, że działa dla nich, oznaczę to jako odpowiedź. – manuel

0

wezmę „odległość od środka do punktu” Być minimalna odległość od punktu do sklepu z tym produktem. Wezmę wynik jako listę (produkt, odległość) dla wszystkich produktów posortowanych według odległości rosnąco. (Komentarz osoby, która umieściła nagrodę, wskazuje, że czasami również chce (produkt, odległość, sklep) posortowane według odległości, a następnie przechowywać w obrębie produktu.)

Każdy model ma odpowiednią tabelę. Pola modelu są kolumnami tabeli. Każdy model/tabela powinien mieć wypełnienie w nazwie (nazwane-) puste, gdzie jego zapisy/wiersze są tymi, które składają prawdziwe oświadczenie.

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

Ponieważ produkt ten sklep (y) jako ManyToManyField to już jest „ProductStore” tabela produktów i sklepów pończoszniczych i sklep już jest „StoreCoord” stół sklepów i ich współrzędne.

Możesz wymieniać pola dowolnego obiektu w filtrze zapytań() dla modelu z manyToManyField.

SQL tego jest prosty:

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 

To powinno być łatwe do map to do query. Jednak nie jestem wystarczająco zaznajomiony z Django, aby podać dokładny kod teraz.Jest to przykład aggregation.

Możesz również uzyskać pomoc, wykonując jednoznacznie subquery.

Możliwe jest również zapytanie do tego przez interfejs raw. Jednak powyższe nazwy nie są odpowiednie dla surowego zapytania Django. Np. Nazwy tabel będą domyślnie APPL_store i APPL_product gdzie APPL jest nazwą aplikacji. Ponadto odległość nie jest Twoim operatorem pointField. Musisz podać odpowiednią funkcję odległości. Ale nie powinieneś pytać o surowy poziom.

+0

Dzięki za odpowiedź. Wysłałem to ponad rok temu i niestety nie mam już dostępu do kodu ani sposobu, aby to przetestować. Jeśli ktoś stwierdzi, że działa dla nich, oznaczę to jako odpowiedź. – manuel

+0

Zrozum. (Niedawno opublikowano na nim nagrodę [Vikas Gulati] (https://stackoverflow.com/users/1283546/vikas-gulati)). – philipxy