From 97255b2a60e01da7afbaee91a4e724d6b3d317af Mon Sep 17 00:00:00 2001 From: rizwanisready Date: Sun, 10 Mar 2024 23:27:22 +0530 Subject: [PATCH] transactions --- manage_communications/admin.py | 44 +++++- manage_communications/api/serializers.py | 3 +- manage_communications/urls.py | 19 +-- manage_communications/views.py | 1 + manage_events/api/serializers.py | 27 +++- manage_events/api/urls.py | 6 + manage_events/api/views.py | 26 +++- ...es_active_favorites_created_by_and_more.py | 127 ++++++++++++++++++ .../0005_alter_eventreview_unique_together.py | 17 +++ manage_events/models.py | 26 +++- manage_wallets/api/serializers.py | 29 +++- manage_wallets/api/urls.py | 19 ++- manage_wallets/api/views.py | 107 --------------- .../manage_communications/feedback_list.html | 4 - 14 files changed, 319 insertions(+), 136 deletions(-) create mode 100644 manage_events/migrations/0004_favorites_active_favorites_created_by_and_more.py create mode 100644 manage_events/migrations/0005_alter_eventreview_unique_together.py diff --git a/manage_communications/admin.py b/manage_communications/admin.py index ca6dd62..c4c4f33 100644 --- a/manage_communications/admin.py +++ b/manage_communications/admin.py @@ -1,5 +1,45 @@ from django.contrib import admin -from .models import TicketIssueType, TicketAttachment, Tickets +from .models import TicketIssueType, TicketAttachment, Tickets, Feedback, ContactUs + + +class FeedbackAdmin(admin.ModelAdmin): + list_display = ("id", "principal", "email", "rating", "short_comment") + list_filter = ("rating", "principal") + search_fields = ("email", "comment") + readonly_fields = ("email",) + + def short_comment(self, obj): + """Create a shortened version of the comment for display in the list.""" + return obj.comment[:50] + "..." if len(obj.comment) > 50 else obj.comment + + short_comment.short_description = "Comment" + + +class ContactUsAdmin(admin.ModelAdmin): + list_display = ( + "name", + "email_address", + "mobile_number", + "subject", + "short_message", + "has_reply", + ) + list_filter = ("subject",) + search_fields = ("name", "email_address", "mobile_number", "message") + readonly_fields = ("name", "email_address", "mobile_number", "subject", "message") + + def short_message(self, obj): + """Create a shortened version of the message for display in the list.""" + return obj.message[:50] + "..." if len(obj.message) > 50 else obj.message + + short_message.short_description = "Message" + + def has_reply(self, obj): + """Indicate whether the contact request has been replied to.""" + return obj.reply is not None + + has_reply.boolean = True + has_reply.short_description = "Replied?" class TicketIssueTypeAdmin(admin.ModelAdmin): @@ -26,6 +66,8 @@ class TicketsAdmin(admin.ModelAdmin): filter_horizontal = ("attachments",) # For the many-to-many relationship +admin.site.register(ContactUs, ContactUsAdmin) +admin.site.register(Feedback, FeedbackAdmin) admin.site.register(TicketIssueType, TicketIssueTypeAdmin) admin.site.register(TicketAttachment, TicketAttachmentAdmin) admin.site.register(Tickets, TicketsAdmin) diff --git a/manage_communications/api/serializers.py b/manage_communications/api/serializers.py index 68e35d4..8809d91 100644 --- a/manage_communications/api/serializers.py +++ b/manage_communications/api/serializers.py @@ -9,7 +9,8 @@ from goodtimes import date_utils class ContactUsSerializer(serializers.ModelSerializer): class Meta: model = ContactUs - fields = "__all__" + # Alternatively, you can specify fields explicitly: + fields = ['name', 'email_address', 'mobile_number', 'subject', 'message'] class TicketIssueTypeSerializer(serializers.ModelSerializer): class Meta: diff --git a/manage_communications/urls.py b/manage_communications/urls.py index 92eaa3d..d66b9ca 100644 --- a/manage_communications/urls.py +++ b/manage_communications/urls.py @@ -4,12 +4,15 @@ from . import views app_name = "manage_communications" urlpatterns = [ - path('contact_us/', views.ContactUsListView.as_view(), name='contact_us_list'), - path('contact_us/reply/', views.ContactUsReplyView.as_view(), name='contact_us_reply'), - - path('tickets/', views.TicketListView.as_view(), name='ticket_list'), - - path('feedback/', views.FeedbackListView.as_view(), name='feedback_list'), - path('feedback/delete/', views.FeedbackDeleteView.as_view(), name='feedback_delete'), - + path("contact_us/", views.ContactUsListView.as_view(), name="contact_us_list"), + path( + "contact_us/reply/", views.ContactUsReplyView.as_view(), name="contact_us_reply" + ), + path("tickets/", views.TicketListView.as_view(), name="ticket_list"), + path("feedback/", views.FeedbackListView.as_view(), name="feedback_list"), + path( + "feedback/delete/", + views.FeedbackDeleteView.as_view(), + name="feedback_delete", + ), ] diff --git a/manage_communications/views.py b/manage_communications/views.py index e4a5bcc..bb1b4fd 100644 --- a/manage_communications/views.py +++ b/manage_communications/views.py @@ -124,6 +124,7 @@ class FeedbackDeleteView(LoginRequiredMixin, generic.View): try: type_obj = self.model.objects.get(id=pk) type_obj.deleted = True + type_obj.active = False type_obj.save() messages.success(request, self.success_message) except self.model.DoesNotExist: diff --git a/manage_events/api/serializers.py b/manage_events/api/serializers.py index b826e2d..90d9371 100644 --- a/manage_events/api/serializers.py +++ b/manage_events/api/serializers.py @@ -6,6 +6,7 @@ from manage_events.models import ( Event, EventCategory, EventImage, + EventReview, Favorites, Venue, PrincipalPreference, @@ -44,6 +45,7 @@ class EventDetailSerializer(serializers.ModelSerializer): venue = VenueSerializer(read_only=True) # Use VenueSerializer for the venue field category = EventCategorySerializer(read_only=True) is_favorited = serializers.SerializerMethodField() + reviews = serializers.SerializerMethodField() class Meta: model = Event @@ -66,6 +68,7 @@ class EventDetailSerializer(serializers.ModelSerializer): "age_group", "images", "is_favorited", + "reviews", ] def get_images(self, obj): @@ -81,6 +84,11 @@ class EventDetailSerializer(serializers.ModelSerializer): return Favorites.objects.filter(event=obj, principal=user).exists() return False + def get_reviews(self, obj): + # related_name is 'reviews' as defined in EventReview model + reviews = obj.reviews.all() + return EventReviewSerializer(reviews, many=True, context=self.context).data + class CreateEventSerializer(serializers.ModelSerializer): images = serializers.ListField( @@ -187,5 +195,20 @@ class EventMasterSerializer(serializers.ModelSerializer): class EventDateRangeSerializer(serializers.Serializer): - start_date = serializers.DateField(format='%Y-%m-%d', input_formats=['%Y-%m-%d']) - end_date = serializers.DateField(format='%Y-%m-%d', input_formats=['%Y-%m-%d']) \ No newline at end of file + start_date = serializers.DateField(format="%Y-%m-%d", input_formats=["%Y-%m-%d"]) + end_date = serializers.DateField(format="%Y-%m-%d", input_formats=["%Y-%m-%d"]) + + +class EventReviewSerializer(serializers.ModelSerializer): + principal = ProfileSerializer(read_only=True) + + class Meta: + model = EventReview + fields = ["id", "event", "principal", "review_text", "rating"] + extra_kwargs = { + "principal": {"read_only": True}, + } + + def create(self, validated_data): + validated_data["principal"] = self.context["request"].user + return super().create(validated_data) diff --git a/manage_events/api/urls.py b/manage_events/api/urls.py index 1a33787..c60a9df 100644 --- a/manage_events/api/urls.py +++ b/manage_events/api/urls.py @@ -91,4 +91,10 @@ urlpatterns = [ ), # My Events path("my-events/", views.MyEventsAPIView.as_view(), name="my-events"), + # Events Review Add and Edit + path( + "event-reviews/", + views.EventReviewCreateAPIView.as_view(), + name="event-review-create", + ), ] diff --git a/manage_events/api/views.py b/manage_events/api/views.py index 79f074d..991049e 100644 --- a/manage_events/api/views.py +++ b/manage_events/api/views.py @@ -2,7 +2,7 @@ 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 import status, generics, mixins from rest_framework.views import APIView from django.conf import settings from accounts.models import IAmPrincipalLocation @@ -22,6 +22,7 @@ from manage_events.api.serializers import ( CreateVenueSerializer, EventCategorySerializer, EventDetailSerializer, + EventReviewSerializer, IAmPrincipalLocationSerializer, PrincipalPreferenceSerializer, VenueSerializer, @@ -32,6 +33,7 @@ from manage_events.models import ( Event, EventCategory, EventPrincipalInteraction, + EventReview, Favorites, PrincipalPreference, Venue, @@ -659,3 +661,25 @@ class EventDateRangeAPIView(APIView): message=constants.SUCCESS, status=status.HTTP_200_OK, ) + + +class EventReviewCreateAPIView(mixins.CreateModelMixin, generics.GenericAPIView): + queryset = EventReview.objects.filter(active=True, deleted=False) + serializer_class = EventReviewSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + if serializer.is_valid(): + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return ApiResponse.success( + "Review created successfully.", + data=serializer.data, + status=status.HTTP_201_CREATED, + ) + else: + return ApiResponse.error( + "Validation error.", + errors=serializer.errors, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/manage_events/migrations/0004_favorites_active_favorites_created_by_and_more.py b/manage_events/migrations/0004_favorites_active_favorites_created_by_and_more.py new file mode 100644 index 0000000..7f81c1a --- /dev/null +++ b/manage_events/migrations/0004_favorites_active_favorites_created_by_and_more.py @@ -0,0 +1,127 @@ +# Generated by Django 5.0.2 on 2024-03-10 14:49 + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_events", "0003_alter_eventprincipalinteraction_unique_together"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="favorites", + name="active", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="favorites", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_created", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="favorites", + name="created_on", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="favorites", + name="deleted", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="favorites", + name="modified_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="favorites", + name="modified_on", + field=models.DateTimeField(auto_now=True), + ), + migrations.CreateModel( + name="EventReview", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("active", models.BooleanField(default=True)), + ("deleted", models.BooleanField(default=False)), + ("created_on", models.DateTimeField(auto_now_add=True)), + ("modified_on", models.DateTimeField(auto_now=True)), + ("review_text", models.TextField()), + ( + "rating", + models.PositiveSmallIntegerField( + choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)] + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_created", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "event", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="reviews", + to="manage_events.event", + ), + ), + ( + "modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "principal", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="event_reviews", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_on"], + "unique_together": {("event", "principal")}, + }, + ), + ] diff --git a/manage_events/migrations/0005_alter_eventreview_unique_together.py b/manage_events/migrations/0005_alter_eventreview_unique_together.py new file mode 100644 index 0000000..97c2e78 --- /dev/null +++ b/manage_events/migrations/0005_alter_eventreview_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.2 on 2024-03-10 17:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_events", "0004_favorites_active_favorites_created_by_and_more"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="eventreview", + unique_together=set(), + ), + ] diff --git a/manage_events/models.py b/manage_events/models.py index 0cf16f8..6bab208 100644 --- a/manage_events/models.py +++ b/manage_events/models.py @@ -126,7 +126,7 @@ class PrincipalPreference(BaseModel): db_table = "user_preference" -class Favorites(models.Model): +class Favorites(BaseModel): principal = models.ForeignKey( IAmPrincipal, on_delete=models.CASCADE, related_name="favorited_by" ) @@ -137,3 +137,27 @@ class Favorites(models.Model): def __str__(self): return f"{self.principal}'s favorite: {self.event.title}" + + +class EventReview(BaseModel): + event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="reviews") + principal = models.ForeignKey( + IAmPrincipal, on_delete=models.CASCADE, related_name="event_reviews" + ) + review_text = models.TextField() + rating = models.PositiveSmallIntegerField( + choices=[ + (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + ] + ) + + class Meta: + # sort reviews, to show latest first by default: + ordering = ["-created_on"] + + def __str__(self): + return f"Review by {self.principal} on {self.event}" diff --git a/manage_wallets/api/serializers.py b/manage_wallets/api/serializers.py index eb07885..40f462b 100644 --- a/manage_wallets/api/serializers.py +++ b/manage_wallets/api/serializers.py @@ -19,7 +19,34 @@ class WalletSerializer(serializers.ModelSerializer): class TransactionSerializer(serializers.ModelSerializer): class Meta: model = models.Transaction - fields = "__all__" + fields = [ + 'id', + 'principal', + 'principal_subscription', + 'transaction_type', + 'payment_method', + 'transaction_status', + 'amount', + 'comment', + 'order_id', + 'product_id', + 'reference_id', + ] + extra_kwargs = { + 'principal': {'read_only': True}, + 'order_id': {'required': False}, + 'product_id': {'required': False}, + 'reference_id': {'required': False}, + } + + def to_representation(self, instance): + # Customize the representation of the serializer, if necessary + representation = super().to_representation(instance) + # For example, to display a human-readable version of the transaction type: + representation['transaction_type_display'] = instance.get_transaction_type_display() + representation['payment_method_display'] = instance.get_payment_method_display() + representation['transaction_status_display'] = instance.get_transaction_status_display() + return representation class MerchantDepositSerializer(serializers.Serializer): diff --git a/manage_wallets/api/urls.py b/manage_wallets/api/urls.py index 933074b..d256144 100644 --- a/manage_wallets/api/urls.py +++ b/manage_wallets/api/urls.py @@ -4,14 +4,13 @@ from . import views app_name = "manage_wallet_api" urlpatterns = [ - - path('become-a-merchant//', views.BecomeAMerchantView.as_view(), name='become_a_merchant'), - path('webhook-test/', views.StripeWebhookTest.as_view(), name='webhook_test'), - path('postman-test/', views.TestWebhookAPI.as_view(), name='postman_test'), - path('postman-test-withdraw/', views.TestWebhookAPIWithdraw.as_view(), name='postman_test_withdraw'), - path('get-wallet/', views.GetWallet.as_view(), name='get_wallet'), - path('is-merchant/', views.IsMerchant.as_view(), name='is_merchant'), - path('transactions/', views.TransactionView.as_view(), name='transactions'), - path('merchant-deposit/', views.MerchantDeposit.as_view(), name='merchant_deposit'), - + path("webhook-test/", views.StripeWebhookTest.as_view(), name="webhook_test"), + path("postman-test/", views.TestWebhookAPI.as_view(), name="postman_test"), + path( + "postman-test-withdraw/", + views.TestWebhookAPIWithdraw.as_view(), + name="postman_test_withdraw", + ), + path("get-wallet/", views.GetWallet.as_view(), name="get_wallet"), + path("get-transaction/", views.TransactionView.as_view(), name="transactions"), ] diff --git a/manage_wallets/api/views.py b/manage_wallets/api/views.py index c12493e..8a73ff1 100644 --- a/manage_wallets/api/views.py +++ b/manage_wallets/api/views.py @@ -24,38 +24,6 @@ from rest_framework_simplejwt.authentication import JWTAuthentication import stripe -class BecomeAMerchantView(APIView): - authentication_classes = [JWTAuthentication] - permission_classes = [IsAuthenticated] - stripe.api_key = settings.STRIPE_SECRET_KEY - - def post(self, request, type): - print(request.data) - amount = 1000 - principal_type = request.data["principal_type"] - try: - payment_intent = stripe.PaymentIntent.create( - amount=100000, - currency="INR", - description="Merchant Registration", - metadata={ - "principal_id": request.user.id, - "principal_type": principal_type, - }, - ) - # payment_intent_id = payment_intent.id # Get the payment_intent ID - - return Response( - { - "client_secret": payment_intent.client_secret, - "message": "Payment intent created successfully", - } - ) - except stripe.error.StripeError as e: - # Handle Stripe-related errors - return Response({"error": str(e)}, status=400) - - @method_decorator(csrf_exempt, name="dispatch") class StripeWebhookTest(APIView): authentication_classes = [] @@ -165,39 +133,6 @@ class GetWallet(APIView): return ApiResponse.error(**error_response) -class IsMerchant(APIView): - authentication_classes = [JWTAuthentication] - permission_classes = [IsAuthenticated] - model = models.Wallet - - def get(self, request): - try: - # Get the Wallet associated with the request.user - wallet_obj = models.Wallet.objects.get(principal_id=request.user.id) - merchant_deposit = wallet_obj.merchant_deposit - if merchant_deposit >= 1.00: - success_response = { - "status": status.HTTP_200_OK, - "message": constants.SUCCESS, - "data": {"is_merchant": True}, - } - return ApiResponse.success(**success_response) - data_response = { - "status": status.HTTP_200_OK, - "message": constants.SUCCESS, - "data": {"is_merchant": False}, - } - return ApiResponse.success(**data_response) - except Exception as e: - # Handle any exceptions and return an error response - error_response = { - "status": status.HTTP_500_INTERNAL_SERVER_ERROR, - "message": constants.INTERNAL_SERVER_ERROR, - "errors": str(e), - } - return ApiResponse.error(**error_response) - - class TransactionView(APIView): authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated] @@ -335,45 +270,3 @@ class TestWebhookAPIWithdraw(APIView): "message": "Webhook received, but payment failed", } return ApiResponse.success(**intent_response) - - -class MerchantDeposit(APIView): - authentication_classes = [JWTAuthentication] - permission_classes = [IsAuthenticated] - - def post(self, request): - # print(request.data) - serializer = serializers.MerchantDepositSerializer(data=request.data) - # print("serializer: ", serializer) - if serializer.is_valid(): - print(serializer.validated_data) - wallet_manager = services.WalletManager( - principal=request.user, - principal_type=IAmPrincipalType.objects.filter( - name=serializer.validated_data["principal_type"] - ).first(), - ) - deposit_amount = serializer.validated_data["amount"] - deposit_field = serializer.validated_data["field"] - - deposit_transaction = wallet_manager.deposit(deposit_amount, deposit_field) - - print("Passed Through principal_wallet Object") - current_balance = models.Wallet.objects.get(principal=request.user) - success_response = { - "status": status.HTTP_200_OK, - "message": "Deposit Received", - "data": { - **serializer.data, - "merchant_deposit": current_balance.merchant_deposit, - }, - } - return ApiResponse.success(**success_response) - - else: - intent_response = { - "status": status.HTTP_400_BAD_REQUEST, - "message": "Deposit Failed", - "errors": serializer.errors, - } - return ApiResponse.error(**intent_response) diff --git a/templates/manage_communications/feedback_list.html b/templates/manage_communications/feedback_list.html index b81732d..a9f76d5 100644 --- a/templates/manage_communications/feedback_list.html +++ b/templates/manage_communications/feedback_list.html @@ -78,9 +78,6 @@ Rating - Reaction Action @@ -100,7 +97,6 @@ {% endfor %} - {{data_obj.feedback_reaction}}