Sulabh Ghimire
Sulabh Ghimire

Reputation: 21

sorting queryset in Django

So, I am learning Django and trying to make a site similar to AirBNB. I have models called lisitngs that has latitude and longitude stored in CharField. My model is as follows:

class Listing(models.Model):

    class BathRoomType(models.TextChoices):
        ATTACHED = 'Attached Bathroom'
        COMMON   = 'Shared Bathroom'
    
    class RoomVentType(models.TextChoices):
        AC      = 'Air Conditioner'
        NO_AC   = 'No Air Conditioner'

    class LisitngType(models.TextChoices):
        ROOM        = 'Room'
        APARTEMENT  = 'Apartement'
        HOUSE       = 'Full House'
    
    user                = models.ForeignKey(User, on_delete=models.CASCADE)
    title               = models.CharField(max_length=255)
    city                = models.ForeignKey(RoomLocation, on_delete=models.CASCADE)
    exact_address       = models.CharField(max_length=255)
    lat                 = models.CharField(max_length=300, blank=False, null=False, default="0")
    lng                 = models.CharField(max_length=300, blank=False, null=False, default="0")
    description         = models.TextField()
    price               = models.IntegerField()
    listing_type        = models.CharField(max_length=20, choices=LisitngType.choices, default=LisitngType.ROOM)
    kitchen_available   = models.BooleanField(default=False)
    kitchen_description = models.TextField(null=True, blank=True)
    bedrooms            = models.IntegerField()
    max_acomodation     = models.IntegerField()
    bathroom_type       = models.CharField(max_length=20, choices=BathRoomType.choices, default=BathRoomType.ATTACHED)
    no_bathrooms        = models.IntegerField()
    room_type           = models.CharField(max_length=30, choices=RoomVentType.choices, default=RoomVentType.AC)
    main_photo          = models.ImageField(upload_to='room_images', default='default_room.jpg')
    photo_1             = models.ImageField(upload_to='room_images', default='default_room.jpg')
    photo_2             = models.ImageField(upload_to='room_images', default='default_room.jpg')
    photo_3             = models.ImageField(upload_to='room_images', default='default_room.jpg')
    is_published        = models.BooleanField(default=False)
    date_created        = models.DateTimeField(default=timezone.now, editable=False)
    slug                = AutoSlugField(populate_from=['title', 'listing_type', 'bathroom_type', 'room_type'])
    rating              = models.IntegerField(default=5)
    approved            = models.BooleanField(default=False)
    total_bookings      = models.IntegerField(default=0)

    def __str__(self):
        return self.title

In my homepage what I want to do is show the listings which are nearby me. For that I have a function named as near_places. This near_place function takes latitude and longitude after querying through the model Listing and returns the distance between the listing and current user accessing the homepage:

import geocoder
from haversine import haversine

def near_places(dest_lat, dest_lng):
    g = geocoder.ip('me')
    origin = tuple(g.latlng)

    destination = (dest_lat, dest_lng)

    distance    = haversine(origin, destination)

    return distance

My homepage function in views.py is as follows:

def home(request):

    objects = Listing.objects.filter(is_published=True, approved=True)
    
    for object in objects:
        lat, lng = float(object.lat), float(object.lng)
        object.distance = near_places(lat, lng)


    return render(request, 'listings/home.html')

As you can see I have looped through the query set and for each data I have calculated the distance and appended in the queryset as distance. Now, I would like to only get 10 items that has lowest distance. How, can I do so.

I have tried to user object = objects.order_by('-distance')[:10] but it gives me error as

FieldError at /
Cannot resolve keyword 'distance' into field. Choices are: The_room_booked, approved, bathroom_type, bedrooms, city, city_id, date_created, description, exact_address, id, is_published, kitchen_available, kitchen_description, lat, listing_type, lng, main_photo, max_acomodation, no_bathrooms, photo_1, photo_2, photo_3, price, rating, reviewsandrating, room_type, slug, title, total_bookings, user, user_id

Any way that I can solve it? Also it takes quite a time to calculate current distance using near_places() function as above.

Upvotes: 0

Views: 60

Answers (1)

Sergey Pugach
Sergey Pugach

Reputation: 5669

You can't do that, because your model doesn't have a distance field and there is no such DB column as well.

What you can do is either

  1. add such field to your model - I don't recommend with your current logic as you will iterate over every instance and send sql request to update every row.

  2. get your queryest, convert it to a list of dicts and then iterate over your list of dicts with your function adding the distance key to it. Then you can sort the list by the python sort function and pass it to the template.

Like:

objects = Listing.objects.filter(is_published=True, approved=True).values('lat', 'lng') # add fields that you need
for obj in objects:
    obj['distance'] = near_places(obj['lat'], obj['lng'])

my_sorted_list = sorted(objects, key=lambda k: k['distance'])

Pass my_sorted_list to your template. You can add reverse=True arg to sorted function if you want another direction sorting.

Upvotes: 1

Related Questions