Merge pull request #62 from WDI-Ideas/development

feat(module_2_filter):Home page advance filter api
This commit is contained in:
BOBBY VISHWAKARMA
2024-07-10 00:45:05 +05:30
committed by GitHub
6 changed files with 277 additions and 12 deletions

View File

@@ -1,4 +1,5 @@
import random
import googlemaps
from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.core.mail import EmailMessage
@@ -6,7 +7,9 @@ from django.utils.html import strip_tags
import math
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
from django.db.models import Case, When
from smtplib import SMTPException
from accounts.models import IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType
from manage_referrals.models import (
GoodTimeCoins,
@@ -495,7 +498,9 @@ class EventFilterService:
events = events.order_by("entry_fee")
elif filter_type == "preference" and principal is not None:
preferences = PrincipalPreference.objects.get(principal=principal)
preferred_categories_ids = preferences.preferred_categories.values_list("id", flat=True)
preferred_categories_ids = preferences.preferred_categories.values_list(
"id", flat=True
)
events = events.filter(category__in=preferred_categories_ids)
return events.distinct()
@@ -508,7 +513,9 @@ class EventFilterService:
if 1 <= category_id <= 10:
events = events.filter(category_id=category_id)
else:
events = Event.objects.none() # Return an empty queryset if the category_id is not valid
events = (
Event.objects.none()
) # Return an empty queryset if the category_id is not valid
return events.distinct()
@@ -680,3 +687,133 @@ class MyEventFilterService:
)
return events
class GoogleMapsservice:
def __init__(self, api_key=None):
self.api_key = api_key or settings.GOOGLE_MAPS_API_KEY
self.client = googlemaps.Client(key=self.api_key)
def get_distance_matrix(self, origin: list, destination: list):
"""
Get the distance matrix from Google Maps API for the given origins and destinations.
Args:
origins (list): List of origin coordinates (latitude, longitude).
destinations (list): List of destination coordinates (latitude, longitude).
Returns:
dict: Distance matrix response from Google Maps API.
"""
return self.client.distance_matrix(origin, destination)
def search_address(self, address):
"""
Search for a list of addresses matching the given address string.
:param address: Address string to search for
:return: List of matching addresses
"""
geocode_result = self.client.geocode(address)
return geocode_result
def get_coordinates_from_address(self, address):
"""
Get the coordinates (latitude and longitude) of the given address.
:param address: Address string to get coordinates for
:return: Coordinates as a tuple (latitude, longitude)
"""
geocode_result = self.client.geocode(address)
if geocode_result:
location = geocode_result[0]['geometry']['location']
return location['lat'], location['lng']
return None
def get_place_id_from_address(self, address):
"""
Get the place ID of the given address.
:param address: Address string to get the place ID for
:return: Place ID
"""
geocode_result = self.client.geocode(address)
if geocode_result:
return geocode_result[0]['place_id']
return None
def get_place_id_from_coordinates(self, latitude, longitude):
"""
Get the place ID of the given coordinates.
:param latitude: Latitude of the location
:param longitude: Longitude of the location
:return: Place ID
"""
reverse_geocode_result = self.client.reverse_geocode((latitude, longitude))
if reverse_geocode_result:
return reverse_geocode_result[0]['place_id']
return None
def search_addresses_containing(self, keyword):
"""
Search for a list of addresses containing the given keyword.
:param keyword: Keyword to search for in addresses
:return: List of matching addresses containing the keyword
"""
geocode_result = self.client.geocode(keyword)
matching_addresses = [result['formatted_address'] for result in geocode_result if keyword.lower() in result['formatted_address'].lower()]
return matching_addresses
def get_nearest_events(self, queryset, latitude, longitude, radius_km=10):
"""
Filter and sort events by their distance to the given latitude and longitude.
Args:
queryset (QuerySet): The queryset of events to filter and sort.
latitude (float): The latitude of the origin point.
longitude (float): The longitude of the origin point.
radius_km (int): The radius in kilometers within which to filter events.
Returns:
QuerySet: The filtered and sorted queryset of events.
"""
# Set the origin to the provided latitude and longitude
origins = [(latitude, longitude)]
# Create a list of destination coordinates for all events with valid venues
destinations = [
(event.venue.latitude, event.venue.longitude)
for event in queryset
if event.venue.latitude and event.venue.longitude
]
# If there is no destination coordinates
if not destinations:
return queryset
# Get the distance matrix from the Google Maps API
matrix = self.get_distance_matrix(origins, destinations)
# Create a dictionary of event IDs and their corresponding distances
distances = {
event.id: element["distance"]["value"]
for event, element in zip(queryset, matrix["rows"][0]["elements"])
if element["status"] == "OK" and element["distance"]["value"] <= radius_km * 1000 # Convert km to meters
}
# Filter the queryset to include only events within the specified radius
queryset = queryset.filter(id__in=distances.keys())
# # Sort the event IDs by their distances in ascending order
# event_ids_by_distance = sorted(distances, key=distances.get)
# # Create a Case/When expression to preserve the order of events by distance
# preserved_order = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(event_ids_by_distance)])
# print(f"preserved_order is {preserved_order}")
# # Order the queryset based on the preserved order
# queryset = queryset.order_by(preserved_order)
return queryset

View File

@@ -81,6 +81,7 @@ THIRD_PARTY_APPS = [
"allauth.socialaccount",
"allauth.socialaccount.providers.apple",
"allauth.socialaccount.providers.google",
"django_filters",
# "django_crontab",
# "django_celery_results",
# "django_celery_beat",

View File

@@ -0,0 +1,34 @@
from django_filters import rest_framework as filters
from django.db.models import Count, Q
from ..models import Event, EventInteractionType
class EventFilter(filters.FilterSet):
"""
FilterSet for Event model.
"""
title = filters.CharFilter(field_name="title", lookup_expr="icontains")
location = filters.CharFilter(field_name="venue__address", lookup_expr="icontains")
category = filters.CharFilter(method="filter_category")
start_date = filters.DateFilter(field_name="start_date", lookup_expr="gte")
end_date = filters.DateFilter(field_name="end_date", lookup_expr="lte")
price_from = filters.DateFilter(field_name="entry_fee", lookup_expr="gte")
price_to = filters.DateFilter(field_name="entry_fee", lookup_expr="lte")
age_group = filters.CharFilter(field_name="age_group", lookup_expr="icontains")
class Meta:
model = Event
fields = [
'title',
'location',
'category',
'start_date',
'end_date',
'price_from',
'price_to',
'age_group',
]
def filter_category(self, queryset, name, value):
category = value.split(',')
return queryset.filter(category__title__in=category)

View File

@@ -51,7 +51,7 @@ class EventCategorySerializer(serializers.ModelSerializer):
class EventListSerializer(serializers.ModelSerializer):
# category = EventCategorySerializer(read_only=True)
category = EventCategorySerializer(read_only=True)
# venue = VenueSerializer(read_only=True)
# draft = serializers.BooleanField(read_only=True)
# tags = TagSerializer(many=True, read_only=True)
@@ -64,9 +64,9 @@ class EventListSerializer(serializers.ModelSerializer):
"description",
"start_date",
"end_date",
# "from_time",
# "to_time",
# "category",
"from_time",
"to_time",
"category",
# "venue",
# "venue_capacity",
"image",
@@ -74,7 +74,7 @@ class EventListSerializer(serializers.ModelSerializer):
# "entry_type",
"entry_fee",
"key_guest",
# "age_group",
"age_group",
# "images",
# "is_favorited",
# "reviews",

View File

@@ -56,6 +56,11 @@ urlpatterns = [
views.PrincipalPreferenceDetailView.as_view(),
name="principal-preferences",
),
path(
"preferences/",
views.EventPreferencesView.as_view(),
name="preferences",
),
# Principal Location
path(
"add-location/",
@@ -123,4 +128,10 @@ urlpatterns = [
views.EventShareView.as_view(),
name="capture_event_share",
),
path(
"events/",
views.EventListView.as_view(),
name="event_filter",
),
]

View File

@@ -1,14 +1,18 @@
import datetime
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
import googlemaps
from rest_framework import status, generics, mixins
from rest_framework.views import APIView
from django.conf import settings
from accounts.models import IAmPrincipalLocation
from goodtimes import constants
from django.db.models import Q
from django.db.models import Q, Count
from taggit.models import Tag
from django.utils.dateparse import parse_date
from goodtimes import services
from goodtimes.services import GoogleMapsservice
from goodtimes.utils import ApiResponse, CapacityError
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
@@ -43,6 +47,7 @@ from manage_events.models import (
import requests
from manage_events.utils import haversine_one, update_principal_location
from .filters import EventFilter
class CreateEventApi(APIView):
@@ -122,7 +127,9 @@ class CreateVenueApi(APIView):
serializer = VenueSerializer(data=data, context={"request": request})
serializer.is_valid(raise_exception=True)
serializer.save(created_by=self.request.user, principal=self.request.user, active=True)
serializer.save(
created_by=self.request.user, principal=self.request.user, active=True
)
# Add additional logic for handling other relationships (e.g., Venue)
return ApiResponse.success(
@@ -568,6 +575,21 @@ class PrincipalPreferenceDetailView(generics.RetrieveAPIView):
)
class EventPreferencesView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = EventCategory
serializer_class = EventCategorySerializer
def get(self, request, *args, **kwargs):
"""Get all event categories for the authenticated user."""
obj = self.model.objects.filter(active=True, deleted=False)
serializer = self.serializer_class(obj, many=True)
return ApiResponse.success(
data=serializer.data, message=constants.SUCCESS, status=status.HTTP_200_OK
)
class EventMasterSearchAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
@@ -688,9 +710,12 @@ class EventFilterByLocationAPIView(APIView):
)
max_distance_km = 10 # Set your desired maximum distance
current_and_future_events_query = Q(active=True, deleted=False, draft=False, created_by__is_active=True,) & (
Q(end_date__gte=today)
)
current_and_future_events_query = Q(
active=True,
deleted=False,
draft=False,
created_by__is_active=True,
) & (Q(end_date__gte=today))
# Get the queryset based on the filter conditions
events_queryset = Event.objects.filter(current_and_future_events_query)
@@ -961,3 +986,60 @@ class EventShareView(APIView):
data="Event shared successfully.",
status=status.HTTP_200_OK,
)
class EventListView(generics.ListAPIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
queryset = Event.objects.filter(active=True, draft=False, deleted=False)
serializer_class = EventListSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = EventFilter
def apply_popularity_latest(self, queryset, ordering):
# Split the ordering fields and remove any leading '-' characters
ordering_fields = [field.lstrip("-") for field in ordering.split(",")]
# Check if 'nearest' is in the ordering fields and remove it as nearest work only for lat and longitude
if "nearest" in ordering_fields:
ordering.replace("nearest", "")
ordering_fields.remove("nearest")
# Annotate the queryset with popularity if 'popularity' is in the ordering fields
if "popularity" in ordering_fields:
queryset = queryset.annotate(popularity=Count("interaction_event"))
# Replace 'latest' with '-created_on' in the ordering fields
ordering = ",".join(
"-created_on" if field == "latest" else f"-{field}"
for field in ordering_fields
)
# Apply the ordering to the queryset
return queryset.order_by(*ordering.split(","))
def get_queryset(self):
queryset = super().get_queryset()
# Sort by nearest location if latitude and longitude are provided
latitude = self.request.query_params.get("latitude")
longitude = self.request.query_params.get("longitude")
if latitude and longitude:
print("latitude fucntion called")
gmaps_service = GoogleMapsservice()
queryset = gmaps_service.get_nearest_events(queryset, float(latitude), float(longitude))
# Apply popularity annotation and ordering if requested
ordering = self.request.query_params.get("ordering")
if ordering:
queryset = self.apply_popularity_latest(queryset, ordering)
return queryset
def get(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
data = self.get_paginated_response(serializer.data)
return ApiResponse.success(message=constants.SUCCESS, data=data)
serializer = self.get_serializer(queryset, many=True)
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)