From a81b5b4f7fe8c61194c727b3614c9ea90fb8e49b Mon Sep 17 00:00:00 2001 From: bobbyvish Date: Wed, 7 Aug 2024 22:58:22 +0530 Subject: [PATCH 1/2] feat(freeapp):add limitation to user preference and search filter --- accounts/api/serializers.py | 83 ++++++++++++++++++- accounts/api/urls.py | 3 +- accounts/api/views.py | 22 ++++- accounts/views.py | 5 +- manage_events/admin.py | 2 + manage_events/api/serializers.py | 7 ++ manage_events/api/urls.py | 2 + manage_events/api/views.py | 59 +++++++++++-- .../migrations/0016_freeusagefeaturelimit.py | 25 ++++++ manage_events/models.py | 25 +++++- manage_subscriptions/models.py | 33 +++++++- 11 files changed, 249 insertions(+), 17 deletions(-) create mode 100644 manage_events/migrations/0016_freeusagefeaturelimit.py diff --git a/accounts/api/serializers.py b/accounts/api/serializers.py index e0cfb27..fd39cc8 100644 --- a/accounts/api/serializers.py +++ b/accounts/api/serializers.py @@ -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) diff --git a/accounts/api/urls.py b/accounts/api/urls.py index 6a51de5..783612e 100644 --- a/accounts/api/urls.py +++ b/accounts/api/urls.py @@ -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//', 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'), diff --git a/accounts/api/views.py b/accounts/api/views.py index f967265..26cf063 100644 --- a/accounts/api/views.py +++ b/accounts/api/views.py @@ -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) \ No newline at end of file diff --git a/accounts/views.py b/accounts/views.py index 703c847..84c03a1 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -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()) diff --git a/manage_events/admin.py b/manage_events/admin.py index 39aae97..fab8c59 100644 --- a/manage_events/admin.py +++ b/manage_events/admin.py @@ -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) diff --git a/manage_events/api/serializers.py b/manage_events/api/serializers.py index 30982b0..042da84 100644 --- a/manage_events/api/serializers.py +++ b/manage_events/api/serializers.py @@ -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 diff --git a/manage_events/api/urls.py b/manage_events/api/urls.py index ad715ce..24e86b7 100644 --- a/manage_events/api/urls.py +++ b/manage_events/api/urls.py @@ -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(), diff --git a/manage_events/api/views.py b/manage_events/api/views.py index 5bfd641..b6a6a9a 100644 --- a/manage_events/api/views.py +++ b/manage_events/api/views.py @@ -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 diff --git a/manage_events/migrations/0016_freeusagefeaturelimit.py b/manage_events/migrations/0016_freeusagefeaturelimit.py new file mode 100644 index 0000000..0d87275 --- /dev/null +++ b/manage_events/migrations/0016_freeusagefeaturelimit.py @@ -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', + }, + ), + ] diff --git a/manage_events/models.py b/manage_events/models.py index 590f1cf..03638ab 100644 --- a/manage_events/models.py +++ b/manage_events/models.py @@ -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) diff --git a/manage_subscriptions/models.py b/manage_subscriptions/models.py index 326f7df..081862e 100644 --- a/manage_subscriptions/models.py +++ b/manage_subscriptions/models.py @@ -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) From 8fb1a7eb41e2ad502eca806ed898f2319587b2f7 Mon Sep 17 00:00:00 2001 From: bobbyvish Date: Wed, 7 Aug 2024 23:52:45 +0530 Subject: [PATCH 2/2] fix(socialmedia):allowed only active event to publish on social media --- manage_events/views.py | 13 ++++++++----- templates/manage_events/event_details.html | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/manage_events/views.py b/manage_events/views.py index 939e708..be68863 100644 --- a/manage_events/views.py +++ b/manage_events/views.py @@ -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 diff --git a/templates/manage_events/event_details.html b/templates/manage_events/event_details.html index 7d5ed7a..80f1be8 100644 --- a/templates/manage_events/event_details.html +++ b/templates/manage_events/event_details.html @@ -62,7 +62,7 @@
-
+

{{ event.brand.title }}

@@ -72,6 +72,7 @@
+ {% if publish %}
@@ -92,6 +93,7 @@
+ {% endif %}