Merge branch 'development' of https://github.com/WDI-Ideas/goodtimes into feature/module_9_coupons

This commit is contained in:
bobbyvish
2024-08-13 15:40:47 +05:30
19 changed files with 468 additions and 81 deletions

View File

@@ -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,
@@ -142,15 +143,9 @@ from phonenumbers import parse, phonenumberutil, NumberParseException
class ProfileSerializer(serializers.ModelSerializer):
profile_photo = serializers.ImageField(required=False)
email = serializers.CharField(read_only=True)
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)
register_complete = serializers.BooleanField(read_only=True)
email = serializers.CharField(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)
phone_no = serializers.CharField(required=True)
class Meta:
@@ -164,18 +159,12 @@ class ProfileSerializer(serializers.ModelSerializer):
"last_name",
"phone_no",
"email",
"invite_count",
"register_complete",
"has_active_subscription",
"has_preferences",
"linkedin_profile",
"youtube_profile",
"facebook_profile",
"instagram_profile",
"website",
"is_active",
"going_events_count",
"interested_events_count",
]
# def validate_phone_no(self, value):
@@ -197,14 +186,53 @@ class ProfileSerializer(serializers.ModelSerializer):
instance.last_name = validated_data.get("last_name", instance.last_name)
return super().update(instance, validated_data)
def get_image_url(self, obj, field_name, request):
image_field = getattr(obj, field_name)
if image_field:
return request.build_absolute_uri(image_field.url)
return ""
def get_principal_type_name(self, obj):
return obj.principal_type.name if obj.principal_type else None
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get("request")
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)
principal_preference_count = serializers.SerializerMethodField(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",
"principal_preference_count",
"going_events_count",
"interested_events_count",
"feature_limit"
]
def get_going_events_count(self, obj):
return EventPrincipalInteraction.objects.filter(
principal=obj, status="going"
principal=obj, status=EventInteractionType.GOING
).count()
def get_interested_events_count(self, obj):
return EventPrincipalInteraction.objects.filter(
principal=obj, status="interested"
principal=obj, status=EventInteractionType.INTERESTED
).count()
def get_invite_count(self, obj):
@@ -215,14 +243,15 @@ 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):
def get_preference(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:
return request.build_absolute_uri(image_field.url)
return ""
def get_principal_preference_count(self, obj):
principal_preference = PrincipalPreference.objects.filter(principal=obj).first()
if principal_preference:
categories = principal_preference.preferred_categories.all()
return categories.count()
return 0
def get_has_active_subscription(self, obj):
subscription_status = {
@@ -234,18 +263,9 @@ class ProfileSerializer(serializers.ModelSerializer):
today = timezone.now().date()
# Attempt to find the active subscription with the furthest grace_period_end_date
latest_subscription = (
PrincipalSubscription.objects.filter(
principal=obj,
is_paid=True,
cancelled=False,
deleted=False,
active=True,
status=SubscriptionStatus.ACTIVE,
)
.order_by("-grace_period_end_date")
.first()
) # Order by descending grace_period_end_date and take the first
latest_subscription = PrincipalSubscription.get_principal_subscription(obj)
print(f"subscrition record {latest_subscription}")
if latest_subscription:
subscription_status["subscription_id"] = (
@@ -267,12 +287,10 @@ class ProfileSerializer(serializers.ModelSerializer):
return subscription_status
def to_representation(self, instance):
data = super().to_representation(instance)
request = self.context.get("request")
data["profile_photo"] = self.get_image_url(instance, "profile_photo", request)
return data
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)

View File

@@ -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'),

View File

@@ -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)

View File

@@ -364,7 +364,12 @@ class IAmPrincipalResourceLinkForm(IAmPrincipalForm):
class CreateCustomerForm(forms.Form):
first_name = forms.CharField(max_length=255, required=True, label='First Name')
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
business_name = forms.CharField(max_length=200, required=True, label="Business Name")
email = forms.EmailField(required=True, label='Email')
phone_no = PhoneNumberField(
widget=forms.TextInput(),
label="Phone No"
)
preferences = forms.ModelMultipleChoiceField(
queryset=EventCategory.objects.all(),
widget=forms.widgets.SelectMultiple(
@@ -383,6 +388,14 @@ class CreateCustomerForm(forms.Form):
label=_('Free period end date'),
help_text=_('Enter the end date of the free period')
)
address_line1 = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40}))
city = forms.CharField(max_length=200, required=False, label="Region")
country = forms.CharField(max_length=200, required=False, label="Country")
website = forms.URLField(max_length=255, required=False, label="Website")
linkedin_profile = forms.URLField(max_length=200, required=False, label="LinkedIn")
facebook_profile = forms.URLField(max_length=200, required=False, label="Facebook")
instagram_profile = forms.URLField(max_length=200, required=False, label="Instagram")
twitter_profile = forms.URLField(max_length=200, required=False, label="Twitter")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -391,7 +404,12 @@ class CreateCustomerForm(forms.Form):
class UpdateCustomerForm(forms.Form):
first_name = forms.CharField(max_length=255, required=True, label='First Name')
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
business_name = forms.CharField(max_length=200, required=True, label="Business Name")
email = forms.EmailField(required=True, label='Email', widget=forms.TextInput(attrs={'readonly': 'readonly'}))
phone_no = PhoneNumberField(
widget=forms.TextInput(),
label="Phone No"
)
preferences = forms.ModelMultipleChoiceField(
queryset=EventCategory.objects.all(),
widget=forms.widgets.SelectMultiple(
@@ -410,6 +428,14 @@ class UpdateCustomerForm(forms.Form):
label=_('Free period end date'),
help_text=_('Enter the end date of the free period')
)
address_line1 = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40}))
city = forms.CharField(max_length=200, required=False, label="Region")
country = forms.CharField(max_length=200, required=False, label="Country")
website = forms.URLField(max_length=255, required=False, label="Website")
linkedin_profile = forms.URLField(max_length=200, required=False, label="LinkedIn")
facebook_profile = forms.URLField(max_length=200, required=False, label="Facebook")
instagram_profile = forms.URLField(max_length=200, required=False, label="Instagram")
twitter_profile = forms.URLField(max_length=200, required=False, label="Twitter")
active = forms.BooleanField(required=False, label='Active', help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",)
def __init__(self, *args, **kwargs):

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-08-11 16:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0013_iamprincipalextendeddata_pwd_changed_post_transfer'),
]
operations = [
migrations.AddField(
model_name='iamprincipal',
name='business_name',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Business Name'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-08-12 08:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0014_iamprincipal_business_name'),
]
operations = [
migrations.AddField(
model_name='iamprincipal',
name='twitter_profile',
field=models.URLField(blank=True, max_length=255, null=True, verbose_name='Principal Twitter'),
),
]

View File

@@ -320,6 +320,8 @@ class IAmPrincipal(AbstractUser):
blank=True,
null=True,
)
business_name = models.CharField(verbose_name="Business Name", max_length=200, blank=True, null=True)
twitter_profile = models.URLField(verbose_name="Principal Twitter", max_length=255, null=True, blank=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []

View File

@@ -627,11 +627,21 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
email=form.cleaned_data.get('email'),
first_name=form.cleaned_data.get('first_name'),
last_name=form.cleaned_data.get('last_name'),
business_name=form.cleaned_data.get('business_name'),
phone_no=form.cleaned_data.get('phone_no'),
password=make_password("goodtimes#2024"),
username=form.cleaned_data.get("email"),
email_verified=True,
register_complete=True,
principal_type=IAmPrincipalType.objects.get(name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER),
address_line1=form.cleaned_data.get("address_line1"),
city=form.cleaned_data.get("city"),
country=form.cleaned_data.get("country"),
website=form.cleaned_data.get("website"),
linkedin_profile=form.cleaned_data.get("linkedin_profile"),
facebook_profile=form.cleaned_data.get("facebook_profile"),
instagram_profile=form.cleaned_data.get("instagram_profile"),
twitter_profile=form.cleaned_data.get("twitter_profile"),
)
# generate referralcode of manager
@@ -648,7 +658,7 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
principal_preference = PrincipalPreference.objects.create(principal=principal_obj)
principal_preference.preferred_categories.set(form.cleaned_data.get("preferences"))
principal_subscription= PrincipalSubscription.objects.create(
principal_subscription = PrincipalSubscription.objects.create(
start_date=form.cleaned_data.get("free_start_date"),
end_date=form.cleaned_data.get("free_end_date"),
principal=principal_obj,
@@ -660,6 +670,7 @@ class CustomerCreateView(LoginRequiredMixin, generic.View):
messages.success(self.request, constants.REGISTRATION_SUCCESS)
return redirect(self.success_url)
except Exception as e:
print("errror is ", e)
messages.error(self.request, str(e))
context = self.get_context_data(form=form)
return render(request, self.template_name, context=context)
@@ -685,12 +696,28 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
def get(self, request, *args, **kwargs):
principal_id = kwargs.get("pk")
principal_obj = IAmPrincipal.objects.get(pk=principal_id)
try:
principal_obj = IAmPrincipal.objects.get(pk=principal_id)
except Exception as e:
messages.error(request, f"No Record of id {principal_id} is found")
return redirect(self.success_url)
print(f"principal address is {principal_obj.address_line1}")
initial_data = {
"first_name": principal_obj.first_name,
"last_name": principal_obj.last_name,
"email": principal_obj.email,
"business_name": principal_obj.business_name,
"phone_no": principal_obj.phone_no,
"address_line1": principal_obj.address_line1,
"city": principal_obj.city,
"country": principal_obj.country,
"website": principal_obj.website,
"linkedin_profile": principal_obj.linkedin_profile,
"facebook_profile": principal_obj.facebook_profile,
"instagram_profile": principal_obj.instagram_profile,
"twitter_profile": principal_obj.twitter_profile,
"active": principal_obj.is_active
}
@@ -714,8 +741,12 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
return render(request, self.template_name, context=context)
def post(self, request, *args, **kwargs):
customer_id = kwargs.get("pk")
principal_obj = IAmPrincipal.objects.get(pk=customer_id)
principal_id = kwargs.get("pk")
try:
principal_obj = IAmPrincipal.objects.get(pk=principal_id)
except Exception as e:
messages.error(request, f"No Record of customer id {principal_id} is found")
return redirect(self.success_url)
form = self.form_class(request.POST)
if not form.is_valid():
context = self.get_context_data(form=form)
@@ -725,6 +756,16 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
# update principal data
principal_obj.first_name = form.cleaned_data.get('first_name')
principal_obj.last_name = form.cleaned_data.get('last_name')
principal_obj.business_name = form.cleaned_data.get("business_name")
principal_obj.phone_no = form.cleaned_data.get("phone_no")
principal_obj.address_line1 = form.cleaned_data.get("address_line1")
principal_obj.city = form.cleaned_data.get("city")
principal_obj.country = form.cleaned_data.get("country")
principal_obj.website = form.cleaned_data.get("website")
principal_obj.linkedin_profile = form.cleaned_data.get("linkedin_profile")
principal_obj.facebook_profile = form.cleaned_data.get("facebook_profile")
principal_obj.instagram_profile = form.cleaned_data.get("instagram_profile")
principal_obj.twitter_profile = form.cleaned_data.get("twitter_profile")
principal_obj.save()
# update principal preferences record
@@ -760,7 +801,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())
@@ -878,26 +922,75 @@ from django.http import HttpResponse
# wb.save(response)
# return response
# from openpyxl.styles import Font
def export_excel_template(request):
# Define the columns and create an empty DataFrame
columns = ['First Name', 'Last Name', 'Email', 'Preferences(should be seperated by comma)', 'Free period start date(YYYY-MM-DD)', 'Free period end date(YYYY-MM-DD)']
# Define the columns for the Customer Registration sheet
columns = [
'First Name',
'Last Name',
'Business Name',
'Email',
'Phone No',
'Preferences (should be separated by comma)',
'Free period start date (YYYY-MM-DD)',
'Free period end date (YYYY-MM-DD)',
'Address',
'Region',
'Country',
'Website',
'LinkedIn',
'Facebook',
'Instagram',
'Twitter',
]
df = pd.DataFrame(columns=columns)
# Create a workbook and select the active worksheet
# Create a workbook and add the Customer Registration worksheet
wb = Workbook()
ws = wb.active
ws.title = 'Customer Registration'
ws_customer = wb.active
ws_customer.title = 'Manager Onboarding'
# Write the column headers
# Write the column headers for the Customer Registration sheet
for col_num, column_title in enumerate(df.columns, 1):
cell = ws.cell(row=1, column=col_num, value=column_title)
cell = ws_customer.cell(row=1, column=col_num, value=column_title)
cell.font = Font(bold=True)
# Create the Readme worksheet
ws_readme = wb.create_sheet(title='Readme')
# Add information about each field to the Readme sheet
readme_data = [
['Field Name', 'Description'],
['First Name', 'The first name of the customer. This is a required field.'],
['Last Name', 'The last name of the customer. This is a required field.'],
['Business Name', 'The official name of the customer\'s business or organization.'],
['Email', 'The email address of the customer. This must be a unique email not already used in the system. This is a required Field'],
['Phone No', 'The business phone number. It should include the country code if applicable.'],
['Category', 'A comma-separated list of event categories the customer is interested in. These should match existing categories in the system. This is a required Field'],
['Free period start date', 'The start date of the customer\'s free trial period, formatted as YYYY-MM-DD.'],
['Free period end date', 'The end date of the customer\'s free trial period, formatted as YYYY-MM-DD. This date must be later than the start date.'],
['Address', 'The complete business address, including street, city, and postal code.'],
['Region', 'The geographic region where the business operates.'],
['Country', 'The country where the business is based.'],
['Website', 'The URL of the business\'s official website. Ensure it includes "http://" or "https://".'],
['LinkedIn', 'The LinkedIn profile URL of the business. Ensure it includes "http://" or "https://".'],
['Facebook', 'The Facebook page URL of the business. Ensure it includes "http://" or "https://".'],
['Instagram', 'The Instagram profile URL of the business. Ensure it includes "http://" or "https://".'],
['Twitter', 'The Twitter handle or profile URL of the business. Ensure it includes "http://" or "https://".'],
]
# Write the Readme data to the Readme worksheet
for row_num, row_data in enumerate(readme_data, 1):
for col_num, cell_value in enumerate(row_data, 1):
cell = ws_readme.cell(row=row_num, column=col_num, value=cell_value)
if row_num == 1: # Make the header bold
cell.font = Font(bold=True)
# Save the workbook to a bytes buffer
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=customer_registration_template.xlsx'
wb.save(response)
return response
class CustomerTransferView(LoginRequiredMixin, generic.View):
@@ -918,7 +1011,7 @@ class CustomerTransferView(LoginRequiredMixin, generic.View):
# Send the email
try:
temp_password="goodtimes#2024"
temp_password = "goodtimes#2024"
principal_obj.password = make_password(temp_password)
principal_obj.save()
email_service.load_template(
@@ -965,12 +1058,19 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
excel_file = request.FILES['file']
wb = load_workbook(filename=excel_file)
ws = wb.active
# Check if the specific sheet exists
if 'Manager Onboarding' not in wb.sheetnames:
form.add_error('file', 'The required sheet "Manager Onboarding" is not present in the uploaded file.')
return render(request, self.template_name, context=context)
# Load the "Manager Onboarding" worksheet
ws = wb['Manager Onboarding']
error_log = []
principals = []
preferences_l = []
preferences_list = []
subscriptions = []
principal_type = IAmPrincipalType.objects.get(name=resource_action.PRINCIPAL_TYPE_EVENT_MANAGER)
free_subscription = Subscription.objects.filter(is_free=True, active=True).first()
@@ -980,7 +1080,7 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
return render(request, self.template_name, context=context)
for idx, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
first_name, last_name, email, preferences, start_date, end_date = row
first_name, last_name, business_name, email, phone_no, preferences, start_date, end_date, address, region, country, website, linkedin, facebook, instagram, twitter = row
print(f"{first_name}, {last_name, email, preferences, start_date, end_date}")
# validate all data
@@ -1014,16 +1114,26 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
username=email.strip(),
email_verified=True,
register_complete=True,
principal_type=principal_type
principal_type=principal_type,
business_name=business_name,
phone_no=str(phone_no),
address_line1=address,
city=region,
country=country,
website=website,
linkedin_profile=linkedin,
facebook_profile=facebook,
instagram_profile=instagram,
twitter_profile=twitter
)
principals.append(principal)
# Collect preferences to be set later
preferences_l.append((principal, event_categories, start_date, end_date))
preferences_list.append((principal, event_categories, start_date, end_date))
if error_log:
context = self.get_context_data(form=form, error_log=error_log)
messages.error(request, "No recore is created check error log and fix the error in the file ")
messages.error(request, "No record is created check error log and fix the error in the file ")
return render(request, self.template_name, context=context)
# Use transaction.atomic to ensure all-or-nothing
@@ -1036,7 +1146,7 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
principals = IAmPrincipal.objects.filter(email__in=[p.email for p in principals])
# Create subscriptions and preferences
for principal, event_categories, start_date, end_date in preferences_l:
for principal, event_categories, start_date, end_date in preferences_list:
principal = principals.get(email=principal.email)
# Generate referral code for the manager

View File

@@ -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)

View File

@@ -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

View File

@@ -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(),

View File

@@ -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

View 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',
},
),
]

View File

@@ -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)

View File

@@ -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

View File

@@ -1,4 +1,5 @@
from datetime import timedelta, timezone
from datetime import timedelta
from django.utils import timezone
from django.db import models
from django.core.exceptions import ValidationError
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
@@ -136,6 +137,34 @@ class PrincipalSubscription(BaseModel):
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)

View File

@@ -35,10 +35,18 @@
<div class="col-md-3">Last Name</div>
<div class="col-md-9">{{principal_obj.last_name}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Business Name</div>
<div class="col-md-9">{{principal_obj.business_name}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Email Address</div>
<div class="col-md-9">{{principal_obj.email}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Phone No</div>
<div class="col-md-9">{{principal_obj.phone_no}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Preferences</div>
<div class="col-md-9">
@@ -61,6 +69,38 @@
<div class="col-md-3">End Date</div>
<div class="col-md-9">{% if principal_subscription %}{{ principal_subscription.end_date }}{% else %}No subscription found{% endif %}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Address</div>
<div class="col-md-9">{{principal_obj.address_line1}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Region</div>
<div class="col-md-9">{{principal_obj.city}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Country</div>
<div class="col-md-9">{{principal_obj.country}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Website</div>
<div class="col-md-9">{{principal_obj.website}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Facebook</div>
<div class="col-md-9">{{principal_obj.facebook_profile}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">LinkedIn</div>
<div class="col-md-9">{{principal_obj.linkedin_profile}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Instagram</div>
<div class="col-md-9">{{principal_obj.instagram_profile}}</div>
</div>
<div class="row mb-3">
<div class="col-md-3">Twitter</div>
<div class="col-md-9">{{principal_obj.twitter_profile}}</div>
</div>
{% if principal_obj.extended_data and not principal_obj.extended_data.is_transferred and principal_obj.extended_data.is_onboarded and principal_obj.principal_type.name == 'event_manager' %}
<div class="col text-end">
<a class="btn btn-dark mb-2 me-4" href="{% url 'accounts:customer_transfer' principal_obj.id %}">

View File

@@ -129,6 +129,7 @@
</td> -->
<td class="text-center">
<ul class="table-controls">
{% if data_obj.extended_data.is_onboarded%}
<li><a href="{% url 'accounts:customer_edit' data_obj.id%}" class="bs-tooltip"
data-bs-toggle="tooltip" data-bs-placement="top" title=""
data-original-title="Edit" data-bs-original-title="Edit"
@@ -141,6 +142,7 @@
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
</path>
</svg></a></li>
{%endif%}
<li><a href="{% url 'accounts:customer_detail' data_obj.id%}" class="bs-tooltip" data-bs-toggle="tooltip" data-bs-placement="top" title="" data-original-title="View" data-bs-original-title="View" aria-label="View">
<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-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
</a></li>

View File

@@ -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>