filtering events

This commit is contained in:
rizwanisready
2024-03-06 22:40:53 +05:30
parent 23bae0eac9
commit 1126f3cbbe
10 changed files with 426 additions and 119 deletions

View File

@@ -8,6 +8,7 @@ from accounts.models import (
IAmPrincipalType,
# IAmPrincipalKYCDetails,
)
from manage_events.models import PrincipalPreference
from manage_referrals.models import (
ReferralCode,
ReferralRecord,
@@ -140,6 +141,7 @@ class ProfileSerializer(serializers.ModelSerializer):
invite_count = serializers.SerializerMethodField(read_only=True)
principal_type_name = serializers.SerializerMethodField(read_only=True)
has_active_subscription = serializers.SerializerMethodField(read_only=True)
has_preferences = serializers.SerializerMethodField(read_only=True)
class Meta:
model = IAmPrincipal
@@ -153,6 +155,7 @@ class ProfileSerializer(serializers.ModelSerializer):
"invite_count",
"register_complete",
"has_active_subscription",
"has_preferences",
]
def update(self, instance, validated_data):
@@ -171,6 +174,9 @@ class ProfileSerializer(serializers.ModelSerializer):
def get_principal_type_name(self, obj):
return obj.principal_type.name if obj.principal_type else None
def get_has_preferences(self, obj):
return PrincipalPreference.objects.filter(principal=obj).exists()
def get_image_url(self, obj, field_name, request):
image_field = getattr(obj, field_name)
if image_field:

View File

@@ -3,6 +3,7 @@ from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.core.mail import EmailMessage
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 smtplib import SMTPException
@@ -16,7 +17,12 @@ from manage_referrals.models import (
from manage_subscriptions.models import PrincipalSubscription, Subscription
from manage_wallets.models import TransactionStatus, Wallet, Transaction
from goodtimes.utils import CapacityError, RandomGenerator
from manage_events.models import Event, EventPrincipalInteraction
from manage_events.models import (
Event,
EventPrincipalInteraction,
PrincipalPreference,
Venue,
)
# from twilio.rest import Client
from django.db.models import Q, Count
@@ -373,3 +379,82 @@ class InteractionCalculator:
interaction["interested"] = "Blue Flames"
return interaction
class EventFilterService:
@staticmethod
def filter_events(filter_type, principal=None):
today = timezone.now().date()
events = Event.objects.none()
current_and_future_events_query = Q(active=True, deleted=False) & (
Q(start_date__lte=today, end_date__gte=today) | Q(start_date__gt=today)
)
if filter_type == "expensive":
events = Event.objects.filter(current_and_future_events_query).order_by(
"-entry_fee"
)
elif filter_type == "cheap":
events = Event.objects.filter(current_and_future_events_query).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
)
events = Event.objects.filter(
category__in=preferred_categories_ids, end_date__gte=today
).distinct()
return events
@staticmethod
def filter_events_by_category(category_id):
today = timezone.now().date()
current_and_future_events_query = Q(active=True, deleted=False) & (
Q(start_date__lte=today, end_date__gte=today) | Q(start_date__gt=today)
)
# Ensure the category_id is valid and within the specified range (1-8)
if 1 <= category_id <= 8:
events = Event.objects.filter(
current_and_future_events_query, category_id=category_id
).distinct()
else:
events = (
Event.objects.none()
) # Return an empty queryset if the category_id is not valid
return events
@staticmethod
def filter_events_for_tomorrow():
today = timezone.now().date()
tomorrow = today + timezone.timedelta(days=1)
# Events that are starting tomorrow, ending tomorrow, or have an end date greater than tomorrow
events_query = (
Q(start_date=tomorrow)
| Q(end_date=tomorrow)
| (Q(start_date__lte=tomorrow) & Q(end_date__gte=tomorrow))
)
events = Event.objects.filter(
events_query, active=True, deleted=False
).distinct()
return events
@staticmethod
def filter_events_for_today():
today = timezone.now().date()
print("Today: ", today)
events = Event.objects.filter(
Q(active=True) & ~Q(deleted=True),
Q(start_date__lte=today) & Q(end_date__gte=today),
)
return events

View File

@@ -9,11 +9,7 @@ urlpatterns = [
views.CreateEventApi.as_view(),
name="add_event",
),
path(
"get-events/<str:filter>/",
views.EventsAPIView.as_view(),
name="get_events",
),
path("get-events/", views.EventsAPIView.as_view(), name="events"),
path(
"event/<int:pk>/",
views.EventDetailAPIView.as_view(),
@@ -29,7 +25,11 @@ urlpatterns = [
views.VenueListView.as_view(),
name="get_venue",
),
path("event-master/search/", views.EventMasterSearchAPIView.as_view(), name="event_master_search"),
path(
"event-master/search/",
views.EventMasterSearchAPIView.as_view(),
name="event_master_search",
),
# Others
path("geocode/", views.GeocodeAPIView.as_view(), name="geocode_api"),
# All Preferences List
@@ -56,4 +56,22 @@ urlpatterns = [
views.IAmPrincipalLocationAPIView.as_view(),
name="add_location",
),
# Favorites
path(
"toggle-favorite/<int:event_id>/",
views.ToggleFavoriteView.as_view(),
name="toggle-favorite",
),
# Going | Interested
path(
"event-status/<int:event_id>/",
views.EventStatusUpdateAPIView.as_view(),
name="event-status-update",
),
# Events filtered by 10 KM
path(
"events/filter-by-location/",
views.EventFilterByLocationAPIView.as_view(),
name="filter-events-by-location",
),
]

View File

@@ -1,5 +1,6 @@
from datetime import timedelta
from django.db import transaction
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status, generics
from rest_framework.views import APIView
@@ -24,14 +25,19 @@ from manage_events.api.serializers import (
VenueSerializer,
)
from manage_events.models import (
EventInteractionType,
EventMaster,
Event,
EventCategory,
EventPrincipalInteraction,
Favorites,
PrincipalPreference,
Venue,
)
import requests
from manage_events.utils import filter_events_by_location
class CreateEventApi(APIView):
authentication_classes = [JWTAuthentication]
@@ -56,7 +62,7 @@ class CreateVenueApi(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = VenueSerializer(data=request.data, context={'request': request})
serializer = VenueSerializer(data=request.data, context={"request": request})
serializer.is_valid(raise_exception=True)
serializer.save(created_by=self.request.user, active=True)
@@ -73,9 +79,10 @@ class EventsAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, filter, *args, **kwargs):
today = timezone.now().date()
params = ["expensive", "cheap", "preference", "today", "tomorrow"]
def get(self, request, *args, **kwargs):
filter = request.query_params.get("filter", None)
category_id = request.query_params.get("category_id", None)
params = ["expensive", "cheap", "preference", "today", "tomorrow", "category"]
if filter not in params:
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
@@ -84,61 +91,26 @@ class EventsAPIView(APIView):
)
try:
if filter == "expensive":
# Constructing a complex query using Q objects
current_and_future_events_query = Q(active=True, deleted=False) & (
Q(start_date__lte=today, end_date__gte=today)
| Q(start_date__gt=today) # Current events # Future events
if filter == "today":
events = services.EventFilterService.filter_events_for_today()
elif filter == "tomorrow":
events = services.EventFilterService.filter_events_for_tomorrow()
elif filter == "category" and category_id is not None:
events = services.EventFilterService.filter_events_by_category(
int(category_id)
)
current_and_future_events = Event.objects.filter(
current_and_future_events_query
).order_by("-entry_fee")
serializer = EventDetailSerializer(
current_and_future_events, context={"request": request}, many=True
else:
events = services.EventFilterService.filter_events(
filter, principal=request.user
)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
elif filter == "cheap":
current_and_future_events_query = Q(active=True, deleted=False) & (
Q(start_date__lte=today, end_date__gte=today)
| Q(start_date__gt=today) # Current events # Future events
)
current_and_future_events = Event.objects.filter(
current_and_future_events_query
).order_by("entry_fee")
serializer = EventDetailSerializer(
current_and_future_events, context={"request": request}, many=True
)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
elif filter == "preference":
preferences = PrincipalPreference.objects.get(principal=request.user)
preferred_categories_ids = preferences.preferred_categories.values_list(
"id", flat=True
)
# Filter events based on user preferences and that are upcoming or ongoing
preference_events = Event.objects.filter(
category__in=preferred_categories_ids, end_date__gte=today
).distinct()
serializer = EventDetailSerializer(
preference_events, context={"request": request}, many=True
)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
serializer = EventDetailSerializer(
events, context={"request": request}, many=True
)
return ApiResponse.success(
status=status.HTTP_200_OK,
message=constants.SUCCESS,
data=serializer.data,
)
except Exception as e:
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
@@ -315,6 +287,7 @@ class IAmPrincipalLocationAPIView(APIView):
user_location = IAmPrincipalLocation.objects.filter(
principal=request.user
).last()
print("user_location: ", user_location)
serializer = IAmPrincipalLocationSerializer(user_location)
return ApiResponse.success(
status=status.HTTP_200_OK,
@@ -332,6 +305,7 @@ class IAmPrincipalLocationAPIView(APIView):
serializer = IAmPrincipalLocationSerializer(
data=request.data, context={"request": request}
)
print("serializer: ", serializer)
if serializer.is_valid():
serializer.save()
return ApiResponse.success(
@@ -381,6 +355,9 @@ class PrincipalPreferenceDetailView(generics.RetrieveAPIView):
class EventMasterSearchAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
serializer = EventMasterSearchSerializer(data=request.data)
if serializer.is_valid():
@@ -406,3 +383,104 @@ class EventMasterSearchAPIView(APIView):
errors=serializer.errors,
status=status.HTTP_400_BAD_REQUEST,
)
class ToggleFavoriteView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, event_id, format=None):
principal = request.user
event = get_object_or_404(Event, pk=event_id)
favorite, created = Favorites.objects.get_or_create(
principal=principal, event=event
)
if not created:
favorite.delete()
return ApiResponse.success(
data="removed",
message=constants.SUCCESS,
status=status.HTTP_200_OK,
)
return ApiResponse.success(
data="added",
message=constants.SUCCESS,
status=status.HTTP_201_CREATED,
)
class EventStatusUpdateAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, event_id):
principal = (
request.user
) # Assuming you're using some form of user authentication
event = get_object_or_404(Event, pk=event_id)
status_requested = request.data.get("status") # 'going' or 'interested'
if status_requested not in EventInteractionType.values:
return ApiResponse.error(
message=constants.FAILURE,
errors="Invalid status provided.",
status=status.HTTP_400_BAD_REQUEST,
)
interaction, created = EventPrincipalInteraction.objects.update_or_create(
principal=principal,
event=event,
defaults={"status": status_requested},
)
if created:
return ApiResponse.success(
data=status_requested,
message=f"Marked as {status_requested}.",
status=status.HTTP_201_CREATED,
)
else:
return ApiResponse.success(
data=status_requested,
message=f"Updated status to {status_requested}.",
status=status.HTTP_200_OK,
)
class EventFilterByLocationAPIView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
principal = request.user
# Fetching user location from IAmPrincipalLocation
try:
user_location = IAmPrincipalLocation.objects.filter(principal=principal).last()
user_lat = user_location.latitude
user_lon = user_location.longitude
except IAmPrincipalLocation.DoesNotExist:
return ApiResponse.error(
errors=constants.FAILURE,
message="User location not set.",
status=status.HTTP_400_BAD_REQUEST,
)
# Now filter events based on user location
events = filter_events_by_location(user_lat, user_lon)
print("events: ", events)
# Assuming you only want current and future events
today = timezone.now().date()
events = events.filter(Q(end_date__gte=today) | Q(start_date__gte=today))
# Serialize and return the filtered events
serializer = EventDetailSerializer(
events, context={"request": request}, many=True
)
return ApiResponse.success(
data=serializer.data,
message=constants.SUCCESS,
status=status.HTTP_200_OK,
)

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.0.2 on 2024-03-06 16:07
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("manage_events", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Favorites",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="favorites",
to="manage_events.event",
),
),
(
"principal",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="favorited_by",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"unique_together": {("principal", "event")},
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.0.2 on 2024-03-06 16:25
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("manage_events", "0002_favorites"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterUniqueTogether(
name="eventprincipalinteraction",
unique_together={("principal", "event")},
),
]

View File

@@ -106,7 +106,7 @@ class EventPrincipalInteraction(models.Model):
)
class Meta:
unique_together = ("principal", "event", "status")
unique_together = ("principal", "event")
class PrincipalPreference(BaseModel):
@@ -120,3 +120,14 @@ class PrincipalPreference(BaseModel):
class Meta:
db_table = "user_preference"
class Favorites(models.Model):
principal = models.ForeignKey(IAmPrincipal, on_delete=models.CASCADE, related_name='favorited_by')
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='favorites')
class Meta:
unique_together = ('principal', 'event')
def __str__(self):
return f"{self.principal}'s favorite: {self.event.title}"

38
manage_events/utils.py Normal file
View File

@@ -0,0 +1,38 @@
import math
from manage_events.models import Event, Venue
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance in kilometers between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(math.radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (
math.sin(dlat / 2) ** 2
+ math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
)
c = 2 * math.asin(math.sqrt(a))
r = 6371 # Radius of earth in kilometers. Use 3956 for miles
return c * r
def filter_events_by_location(user_lat, user_lon, radius_km=10):
venues_within_radius = []
# Check each venue to see if it's within the radius
for venue in Venue.objects.filter(deleted=False, active=True):
print("venue: ", venue)
distance = haversine(user_lon, user_lat, venue.longitude, venue.latitude)
print("distance: ", distance)
if distance <= radius_km:
venues_within_radius.append(venue.id)
print("venues_within_radius: ", venues_within_radius)
# Filter events based on the venues within the radius
events = Event.objects.filter(venue__id__in=venues_within_radius)
return events

View File

@@ -299,25 +299,26 @@ class EventView(LoginRequiredMixin, generic.ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
# Paginate the queryset
queryset = self.get_queryset()
page_obj = Paginator(queryset, self.paginate_by)
page_number = self.request.GET.get(
"page"
) # Get current page from URL parameter
page_obj = page_obj.get_page(page_number)
context.update(
{
"events": page_obj.object_list,
"paginator": page_obj,
"is_paginated": page_obj.has_other_pages(), # Check if there are more pages
}
)
return context
# # Paginate the queryset
# queryset = self.get_queryset()
# page_obj = Paginator(queryset, self.paginate_by)
# page_number = self.request.GET.get(
# "page"
# ) # Get current page from URL parameter
# page_obj = page_obj.get_page(page_number)
# context.update(
# {
# "events": page_obj.object_list,
# "paginator": page_obj,
# "is_paginated": page_obj.has_other_pages(), # Check if there are more pages
# }
# )
# return context
class EventDetailView(generic.DetailView):
model = Event

View File

@@ -1,8 +1,8 @@
{% extends 'layout/base_template.html' %}
{% load static %}
{% block stylesheet %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
<!-- include required css cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_css.html" %}
{% endblock %}
@@ -12,7 +12,7 @@
<div class="col-lg-12">
<div class="row">
<div class="col-sm-6">
<h3>Manage Events</h3>
<h3>Manage Subscriptions</h3>
</div>
<div class="col-sm-6 text-md-end">
<!--
@@ -24,10 +24,11 @@
<a class="btn btn-primary mb-2" href="{% url 'manage_events:event_add' %}">Add Event</a>
<a class="btn btn-primary mb-2" href="{% url 'manage_events:event_category_list' %}">Event Category</a>
<a class="btn btn-primary mb-2" href="{% url 'manage_events:event_master_list' %}">Event Master</a>
</div>
</div>
<div class="row layout-spacing">
<div class="col-lg-12">
<div class="statbox widget box box-shadow">
@@ -60,7 +61,8 @@
{% for data_obj in event_obj %}
<tr role="row">
<td class="checkbox-column text-center sorting_1"><a
href="{% url 'manage_events:event_detail' data_obj.id %}">{{data_obj.id}}</a></td>
href="{% url 'manage_events:event_detail' data_obj.id %}">{{data_obj.id}}</a>
</td>
<td>{{data_obj.title}}</td>
<td>{{data_obj.start_date}}</td>
<td>{{data_obj.end_date}}</td>
@@ -106,39 +108,39 @@
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
<script>
c3 = $('#style-3').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
multiCheck(c3);
</script>
{% endblock %}
{% endblock content %}
{% block javascript %}
<!-- include required js cdn link through html here -->
{% include "cdn_through_html/datatable_cdn_js.html" %}
<script>
c3 = $('#style-3').DataTable({
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
"<'table-responsive'tr>" +
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
"oLanguage": {
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
"sInfo": "Showing page _PAGE_ of _PAGES_",
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
"sSearchPlaceholder": "Search...",
"sLengthMenu": "Results : _MENU_",
},
"stripeClasses": [],
"lengthMenu": [5, 10, 20, 50],
"pageLength": 10
});
multiCheck(c3);
</script>
{% endblock %}