@@ -10,7 +10,8 @@ from accounts.models import (
|
||||
IAmPrincipalType,
|
||||
# IAmPrincipalKYCDetails,
|
||||
)
|
||||
from manage_events.models import EventPrincipalInteraction, PrincipalPreference
|
||||
|
||||
from manage_events.models import EventInteractionType, EventPrincipalInteraction, FreeUsageFeatureLimit, PrincipalPreference
|
||||
from manage_referrals.models import (
|
||||
ReferralCode,
|
||||
ReferralRecord,
|
||||
@@ -267,6 +268,86 @@ class ProfileSerializer(serializers.ModelSerializer):
|
||||
data["profile_photo"] = self.get_image_url(instance, "profile_photo", request)
|
||||
return data
|
||||
|
||||
class ProfileExtendedDataSerializer(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)
|
||||
preference = serializers.SerializerMethodField(read_only=True)
|
||||
register_complete = serializers.BooleanField(read_only=True)
|
||||
is_active = serializers.BooleanField(read_only=True)
|
||||
going_events_count = serializers.SerializerMethodField(read_only=True)
|
||||
interested_events_count = serializers.SerializerMethodField(read_only=True)
|
||||
feature_limit = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = IAmPrincipal
|
||||
fields = [
|
||||
"principal_type_name",
|
||||
"invite_count",
|
||||
"register_complete",
|
||||
"has_active_subscription",
|
||||
"preference",
|
||||
"is_active",
|
||||
"going_events_count",
|
||||
"interested_events_count",
|
||||
"feature_limit"
|
||||
]
|
||||
|
||||
def get_going_events_count(self, obj):
|
||||
return EventPrincipalInteraction.objects.filter(
|
||||
principal=obj, status=EventInteractionType.GOING
|
||||
).count()
|
||||
|
||||
def get_interested_events_count(self, obj):
|
||||
return EventPrincipalInteraction.objects.filter(
|
||||
principal=obj, status=EventInteractionType.INTERESTED
|
||||
).count()
|
||||
|
||||
def get_invite_count(self, obj):
|
||||
if obj:
|
||||
return ReferralRecord.get_invite_count(obj)
|
||||
return 0
|
||||
|
||||
def get_principal_type_name(self, obj):
|
||||
return obj.principal_type.name if obj.principal_type else None
|
||||
|
||||
def get_preference(self, obj):
|
||||
return PrincipalPreference.objects.filter(principal=obj).exists()
|
||||
|
||||
def get_has_active_subscription(self, obj):
|
||||
subscription_status = {
|
||||
"has_active_subscription": False,
|
||||
"in_grace_period": False,
|
||||
"grace_period_end_date": None,
|
||||
}
|
||||
today = timezone.now().date()
|
||||
|
||||
# Attempt to find the active subscription with the furthest grace_period_end_date
|
||||
latest_subscription = PrincipalSubscription.get_principal_subscription(obj)
|
||||
|
||||
print(f"subscrition record {latest_subscription}")
|
||||
|
||||
if latest_subscription:
|
||||
# Check if we're within the grace period
|
||||
if today <= latest_subscription.grace_period_end_date:
|
||||
subscription_status["has_active_subscription"] = (
|
||||
today <= latest_subscription.end_date
|
||||
)
|
||||
subscription_status["in_grace_period"] = (
|
||||
latest_subscription.end_date
|
||||
< today
|
||||
<= latest_subscription.grace_period_end_date
|
||||
)
|
||||
subscription_status["grace_period_end_date"] = (
|
||||
latest_subscription.grace_period_end_date
|
||||
)
|
||||
|
||||
return subscription_status
|
||||
|
||||
def get_feature_limit(self, obj):
|
||||
from manage_events.api.serializers import FreeUsageFeatureLimitSerializer
|
||||
obj = FreeUsageFeatureLimit.objects.first()
|
||||
return FreeUsageFeatureLimitSerializer().to_representation(obj)
|
||||
|
||||
# class PrincipalKYCDetailsSerializer(serializers.ModelSerializer):
|
||||
# aadhar_front_image = serializers.ImageField(required=False)
|
||||
|
||||
@@ -17,7 +17,8 @@ urlpatterns = [
|
||||
path('request-otp/', views.OtpRequestView.as_view(), name='send_otp'),
|
||||
path('verify-otp/', views.OTPVerificationView.as_view(), name='send_otp'),
|
||||
|
||||
# path('profile/view/<str:principal_type>/', views.ProfileView.as_view(), name='profile_veiw'),
|
||||
path('profile/extended-data/', views.ProfileExtendedView.as_view(), name='profile_veiw'),
|
||||
|
||||
path('profile/view/', views.ProfileView.as_view(), name='profile_veiw'),
|
||||
path('profile/edit/', views.ProfileView.as_view(), name='profile_edit'),
|
||||
path('profile/password-reset/', views.ProfilePasswordResetView.as_view(), name='password_reset'),
|
||||
|
||||
@@ -44,6 +44,7 @@ from .serializers import (
|
||||
EmailSerializer,
|
||||
IAmPrincipalExtendedDataSerializer,
|
||||
PlayerIDSerializer,
|
||||
ProfileExtendedDataSerializer,
|
||||
RegistrationPasswordSerializer,
|
||||
RegistrationSerializer,
|
||||
ReferralCodeSerializer,
|
||||
@@ -538,6 +539,21 @@ class ProfileView(APIView):
|
||||
)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
|
||||
class ProfileExtendedView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
model = IAmPrincipal
|
||||
serializer = ProfileExtendedDataSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
serializer = self.serializer(instance=request.user)
|
||||
success_response = {
|
||||
"status": status.HTTP_200_OK,
|
||||
"message": constants.SUCCESS,
|
||||
"data": serializer.data,
|
||||
}
|
||||
return ApiResponse.success(**success_response)
|
||||
|
||||
|
||||
class ProfilePasswordResetView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
@@ -1003,7 +1019,7 @@ class AccountTransferCheckView(APIView):
|
||||
"errors": str(e),
|
||||
}
|
||||
return ApiResponse.error(**error_response)
|
||||
|
||||
|
||||
serializer = self.serializer_class(obj)
|
||||
print("serializer data", serializer)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
@@ -1032,6 +1048,4 @@ class AccountTransferCheckView(APIView):
|
||||
obj.pwd_changed_post_transfer = True
|
||||
obj.save()
|
||||
serializer = self.serializer_class(obj)
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
|
||||
|
||||
return ApiResponse.success(message=constants.SUCCESS, data=serializer.data)
|
||||
@@ -760,7 +760,10 @@ class CustomerDetailView(LoginRequiredMixin, generic.DetailView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
principal_obj = IAmPrincipal.objects.get(pk=kwargs.get("pk"))
|
||||
principal_preference = PrincipalPreference.objects.get(principal_id=principal_obj.id)
|
||||
try:
|
||||
principal_preference = PrincipalPreference.objects.get(principal_id=principal_obj.id)
|
||||
except Exception as e:
|
||||
principal_preference = None
|
||||
principal_subscription = PrincipalSubscription.objects.filter(principal=principal_obj).order_by("-start_date").first()
|
||||
return render(request, self.template_name, locals())
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from .models import (
|
||||
EventShare,
|
||||
EventView,
|
||||
Favorites,
|
||||
FreeUsageFeatureLimit,
|
||||
Venue,
|
||||
EventMaster,
|
||||
Event,
|
||||
@@ -131,3 +132,4 @@ admin.site.register(EventCategory, EventCategoryAdmin)
|
||||
admin.site.register(Venue, VenueAdmin)
|
||||
admin.site.register(EventMaster, EventMasterAdmin)
|
||||
admin.site.register(AgeGroups)
|
||||
admin.site.register(FreeUsageFeatureLimit)
|
||||
|
||||
@@ -14,11 +14,18 @@ from manage_events.models import (
|
||||
EventPrincipalInteraction,
|
||||
EventReview,
|
||||
Favorites,
|
||||
FreeUsageFeatureLimit,
|
||||
Venue,
|
||||
PrincipalPreference,
|
||||
)
|
||||
|
||||
|
||||
class FreeUsageFeatureLimitSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FreeUsageFeatureLimit
|
||||
fields = ['id', 'category_limit']
|
||||
|
||||
|
||||
class EventImageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = EventImage
|
||||
|
||||
@@ -4,6 +4,7 @@ from . import views
|
||||
app_name = "manage_events_api"
|
||||
|
||||
urlpatterns = [
|
||||
path('free/feature-limit/', views.FreeUsageFeatureLimitView.as_view(), name='feature-limit'),
|
||||
path(
|
||||
"add-event/",
|
||||
views.CreateEventApi.as_view(),
|
||||
@@ -134,6 +135,7 @@ urlpatterns = [
|
||||
name="age_group_list"
|
||||
),
|
||||
|
||||
# event list with filter
|
||||
path(
|
||||
"events/",
|
||||
views.EventListView.as_view(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import googlemaps
|
||||
from rest_framework import status, generics, mixins
|
||||
from rest_framework.views import APIView
|
||||
from django.conf import settings
|
||||
from accounts import resource_action
|
||||
from accounts.models import IAmPrincipalLocation
|
||||
from goodtimes import constants
|
||||
from django.db.models import Q, Count
|
||||
@@ -27,6 +28,7 @@ from manage_events.api.serializers import (
|
||||
EventCategorySerializer,
|
||||
EventDetailSerializer,
|
||||
EventReviewSerializer,
|
||||
FreeUsageFeatureLimitSerializer,
|
||||
IAmPrincipalLocationSerializer,
|
||||
PrincipalPreferenceSerializer,
|
||||
VenueSerializer,
|
||||
@@ -43,14 +45,30 @@ from manage_events.models import (
|
||||
EventShare,
|
||||
EventView,
|
||||
Favorites,
|
||||
FreeUsageFeatureLimit,
|
||||
PrincipalPreference,
|
||||
Venue,
|
||||
)
|
||||
import requests
|
||||
|
||||
from manage_events.utils import haversine_one, update_principal_location
|
||||
from manage_subscriptions.models import PrincipalSubscription
|
||||
from .filters import EventFilter
|
||||
|
||||
class FreeUsageFeatureLimitView(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
model = FreeUsageFeatureLimit
|
||||
serializer_class = FreeUsageFeatureLimitSerializer
|
||||
|
||||
def get(self, request):
|
||||
obj = self.model.objects.first()
|
||||
serializer = self.serializer_class(obj)
|
||||
return ApiResponse.success(
|
||||
status=status.HTTP_200_OK,
|
||||
message=constants.SUCCESS,
|
||||
data=serializer.data,
|
||||
)
|
||||
|
||||
class CreateEventApi(APIView):
|
||||
authentication_classes = [JWTAuthentication]
|
||||
@@ -539,6 +557,27 @@ class PrincipalPreferenceView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
principal = request.user
|
||||
# Check if the principal has a subscription
|
||||
if not PrincipalSubscription.has_principal_subscription(principal):
|
||||
# Get the preferred categories from the request data
|
||||
preferred_categories = request.data.get("preferred_categories", [])
|
||||
|
||||
# Get the category limit for free usage
|
||||
category_limit = FreeUsageFeatureLimit.get_category_limit()
|
||||
|
||||
# Check if the principal is an event user and has exceeded the category limit
|
||||
if principal.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_USER and len(preferred_categories) > category_limit:
|
||||
# Create an error message indicating that a paid subscription is required
|
||||
error_message = f"Upgrade to paid subscription to select more than {category_limit} categories."
|
||||
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
message=error_message,
|
||||
errors=error_message,
|
||||
)
|
||||
|
||||
serializer = PrincipalPreferenceSerializer(
|
||||
data=request.data, context={"request": request}
|
||||
)
|
||||
@@ -1009,7 +1048,12 @@ class EventListView(generics.ListAPIView):
|
||||
filterset_class = EventFilter
|
||||
|
||||
def get_queryset(self):
|
||||
# Base queryset filtering active, non-draft, non-deleted events with end date in the future
|
||||
"""
|
||||
Returns a queryset of events filtered by the user's preferences and subscription status.
|
||||
"""
|
||||
principal = self.request.user
|
||||
|
||||
# Filter the base queryset to only include active, non-draft, non-deleted events with an end date in the future
|
||||
queryset = Event.objects.filter(
|
||||
active=True,
|
||||
draft=False,
|
||||
@@ -1017,11 +1061,14 @@ class EventListView(generics.ListAPIView):
|
||||
end_date__gte=timezone.now().date()
|
||||
)
|
||||
|
||||
if not self.request.query_params:
|
||||
preferences = PrincipalPreference.objects.get(principal=self.request.user)
|
||||
preferred_categories_ids = preferences.preferred_categories.values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
# If no filter is applied and the user does not have a subscription,
|
||||
# only show events that match the user's preferred categories
|
||||
if not self.request.query_params or not PrincipalSubscription.has_principal_subscription(principal):
|
||||
# Get the user's preferred categories
|
||||
preferences = PrincipalPreference.objects.get(principal=principal)
|
||||
preferred_categories_ids = preferences.preferred_categories.values_list("id", flat=True)
|
||||
|
||||
# Filter the queryset to only include events in the user's preferred categories
|
||||
queryset = queryset.filter(category__in=preferred_categories_ids).order_by("start_date")
|
||||
|
||||
return queryset
|
||||
|
||||
25
manage_events/migrations/0016_freeusagefeaturelimit.py
Normal file
25
manage_events/migrations/0016_freeusagefeaturelimit.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.0.2 on 2024-08-05 10:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('manage_events', '0015_agegroups'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FreeUsageFeatureLimit',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('category_limit', models.PositiveIntegerField(default=3, help_text='The maximum number of categories that free app users can select.')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Free Usage Feature Limit',
|
||||
'verbose_name_plural': 'Free Usage Feature Limits',
|
||||
'db_table': 'free_usage_feature_limit',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,12 +1,33 @@
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
from accounts.models import BaseModel, IAmPrincipal
|
||||
from django.db import transaction
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
# from django.contrib.gis.db import models as gis_models
|
||||
|
||||
class FreeUsageFeatureLimit(models.Model):
|
||||
category_limit = models.PositiveIntegerField(
|
||||
default=3,
|
||||
help_text="The maximum number of categories that free app users can select."
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "free_usage_feature_limit"
|
||||
verbose_name = "Free Usage Feature Limit"
|
||||
verbose_name_plural = "Free Usage Feature Limits"
|
||||
|
||||
def __str__(self):
|
||||
return f"Free usage limit: {self.category_limit} categories"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk and FreeUsageFeatureLimit.objects.exists():
|
||||
raise ValidationError("There can only be one FreeUsageFeatureLimit instance.")
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_category_limit(cls):
|
||||
return cls.objects.values_list('category_limit', flat=True).first()
|
||||
|
||||
# Create your models here.
|
||||
class EventCategory(BaseModel):
|
||||
title = models.CharField(max_length=255)
|
||||
image = models.ImageField(upload_to="event_category", null=True, blank=True)
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
from goodtimes import constants
|
||||
from django.contrib.auth import get_user_model
|
||||
from datetime import date
|
||||
|
||||
# Create your views here.
|
||||
|
||||
@@ -362,24 +363,26 @@ class EventDetailView(generic.DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["page_name"] = self.page_name
|
||||
event_id = self.object.id # Get the current event's ID
|
||||
event = self.object # Get the current event's ID
|
||||
|
||||
# Separate count for interested and going
|
||||
interested_count = EventPrincipalInteraction.objects.filter(
|
||||
event_id=event_id, status="interested"
|
||||
event=event, status="interested"
|
||||
).count()
|
||||
going_count = EventPrincipalInteraction.objects.filter(
|
||||
event_id=event_id, status="going"
|
||||
event=event, status="going"
|
||||
).count()
|
||||
|
||||
context["interested_count"] = interested_count
|
||||
context["going_count"] = going_count
|
||||
today = date.today()
|
||||
context["publish"] = not event.draft and event.active and event.end_date >= today
|
||||
|
||||
# Reviews for the event
|
||||
context["reviews"] = self.object.reviews.all()
|
||||
context["reviews"] = event.reviews.all()
|
||||
|
||||
# Images of the event
|
||||
context["images"] = self.object.event_images.all()
|
||||
context["images"] = event.event_images.all()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from datetime import timedelta, timezone
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
from django.db import models
|
||||
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -77,13 +78,41 @@ class PrincipalSubscription(BaseModel):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.subscription} - {self.principal.first_name}"
|
||||
|
||||
|
||||
def generate_order_id(email):
|
||||
return f"order_{str(timezone.localtime().timestamp())}{str(email)}"
|
||||
|
||||
def generate_grace_period_end_date(date):
|
||||
return date + timedelta(days=15)
|
||||
|
||||
@classmethod
|
||||
def has_principal_subscription(cls, principal):
|
||||
return cls.get_active_princial_subscription(principal).exists()
|
||||
|
||||
@classmethod
|
||||
def get_active_princial_subscription(cls, principal):
|
||||
return cls.objects.filter(
|
||||
principal=principal,
|
||||
is_paid=True,
|
||||
cancelled=False,
|
||||
deleted=False,
|
||||
active=True,
|
||||
status=SubscriptionStatus.ACTIVE,
|
||||
grace_period_end_date__gt=timezone.now().date(),
|
||||
)
|
||||
|
||||
# need to improve this
|
||||
@classmethod
|
||||
def get_principal_subscription(cls, principal):
|
||||
return cls.objects.filter(
|
||||
principal=principal,
|
||||
is_paid=True,
|
||||
cancelled=False,
|
||||
deleted=False,
|
||||
active=True,
|
||||
status=SubscriptionStatus.ACTIVE,
|
||||
).order_by("-grace_period_end_date").first()
|
||||
|
||||
|
||||
class WebhookEvent(BaseModel):
|
||||
event_id = models.CharField(max_length=255, unique=True, db_index=True)
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-{{ publish|yesno:'8,12' }}">
|
||||
<div class="card mb-3" style="border-radius: 20px; box-shadow: 0 4px 8px rgba(0,0,0,.1);">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ event.brand.title }}</h2>
|
||||
@@ -72,6 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if publish %}
|
||||
<div class="col-md-4">
|
||||
<div class="card mb-3" style="border-radius: 20px; box-shadow: 0 4px 8px rgba(0,0,0,.1);">
|
||||
<div class="card-body">
|
||||
@@ -92,6 +93,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user