Question

I don't know how to do it... it seems simple but I was not able to do it... please give me a hint. Here is the model:

class PointOfInterest(models.Model):
    name = models.CharField(max_length=100)
    geom = geomodels.PointField(srid=4326)
    ...

class Event(models.Model):
    venue = models.ForeignKey('PointOfInterest',related_name='events')
    start = models.DateTimeField()
    ...

The event table contains many event and there could be more than event in the same PointOfInterest. My goal is to select the first 10 events starting from today (there are obviously events in the past) near my place. For each PointOfInterest the new list has only one entry. So I started selecting the events:

from django.db.models import Max, Min
from datetime import datetime, date, timedelta
from django.contrib.gis.measure import D
# this is my place...
pnt=Point(15.0,43.2)
# this is the date range...
start_date = datetime(date.today().year,date.today().month,date.today().day,0,0,0)
end_date = start_date + timedelta(days=30)
qs1 = Event.objects.filter(venue__geom__distance_lte=(pnt, D(km=10)))
qs2 = filter(start__range=(start_date, end_date))
qs2 = qs1.filter(start__range=(start_date, end_date))

The list now contains all the events requested but there are also more than one event for the same place... but I need only the upcoming one. The main problem is that this list could be big and my final step would be to get only the 10 upcoming events. I tried to use distinct('venue') but then I cannot order_by('start'). So I don't know how to proceed...

I also tried to go the other way around...

qs1 = PointOfInterest.objects.filter(geom__distance_lte=(pnt, D(km=10)))
qs2 = qs1.annotate(minDate=Min('events__start'))
qs3 = qs2.filter(minDate__range=(start_date, end_date))

This list give me all the PointOfInterest (129 in my case... while the previous list gave me 137 events... in fact there are 8 place with 2 events in the selected queries and I need to keep for each place only the upcoming one. Finally I'd like to order_by('start') and keep only few of them).

Also in this case I tried a lot but nothing worked... anyone can help? Thanks

EDIT

Almost there. Following the suggestion from EvilX I changed the selection but events__start__range=(start_date, end_date) run on the places, so when I execute the 2 extra he suggested, I get the first event associated to each place (also if it's in the past). I had to change slightly the SQL query. The first attempt was this:

.extra(select={'event_pk': '
    SELECT start    
    FROM app_event_event 
    AS evt 
    WHERE evt.venue_id=app_place_pointofinterest.id AND evt.start >= \'2014/05/04\' 
    ORDER BY start 
    LIMIT 1'})

which works... then I tried to follow the django documentation to pass a parameter to the SQL query like this:

.extra(select={'event_pk': '
    SELECT start 
    FROM app_event_event 
    AS evt
    WHERE evt.venue_id=app_place_pointofinterest.id AND evt.start >= %s 
    ORDER BY start 
    LIMIT 1'}, params=['2014/05/04']) 

But here I get an error: StopIteration; I tried different combination with and without the escape character but nothing worked... so I need some more advice. Thanks

Was it helpful?

Solution

Try to use extra and two queries.

points = PointOfInterest.objects.filter(geom__distance_lte=(pnt, D(km=10)), events__start__range=(start_date, end_date)).extra(select={'event_pk': 'SELECT event.id FROM app name_event AS event where event.venue_pk=appname_pointofinterest.id ORDER BY start LIMIT 1'}).extra(select={'event_start': 'SELECT event.start_date FROM app name_event AS event where event.venue_pk=appname_pointofinterest.id ORDER BY start LIMIT 1'}).order_by('event_start')[:10]
ids = []
for point in points:
    ids.append(point.event_pk)
events = Event.objects.select_related('venue').filter(pk__in=ids)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top