diff --git a/manage_events/admin.py b/manage_events/admin.py index ec4bfc8..ff0a938 100644 --- a/manage_events/admin.py +++ b/manage_events/admin.py @@ -1,5 +1,13 @@ from django.contrib import admin -from .models import EventCategory, EventView, Venue, EventMaster, Event, EventPrincipalInteraction +from .models import ( + EventCategory, + EventShare, + EventView, + Venue, + EventMaster, + Event, + EventPrincipalInteraction, +) # Register your models here. @@ -78,7 +86,7 @@ class EventAdmin(admin.ModelAdmin): }, ), ) - filter_horizontal = () # Use this if there are many-to-many fields + filter_horizontal = () # if there are many-to-many fields raw_id_fields = ("venue", "category", "event_master") @@ -88,17 +96,24 @@ class EventPrincipalInteractionAdmin(admin.ModelAdmin): search_fields = ( "principal__name", "event__title", - ) # Adjust these field lookups according to your models. + ) class EventViewAdmin(admin.ModelAdmin): - list_display = ('id', 'event', 'principal', 'view_date', 'location') - search_fields = ('event__title', 'principal__email', 'location') - list_filter = ('view_date', 'location', 'event__title', 'principal__email') - ordering = ('-view_date',) - readonly_fields = ('id',) + list_display = ("id", "event", "principal", "view_date", "location") + search_fields = ("event__title", "principal__email", "location") + list_filter = ("id", "view_date", "location", "event__title", "principal__email") + ordering = ("-view_date",) + readonly_fields = ("id",) +class EventShareAdmin(admin.ModelAdmin): + list_display = ("id", "event", "principal", "created_on") + search_fields = ("event__title", "principal__username") + list_filter = ("id", "event", "principal", "created_on") + + +admin.site.register(EventShare, EventShareAdmin) admin.site.register(EventView, EventViewAdmin) admin.site.register(EventPrincipalInteraction, EventPrincipalInteractionAdmin) admin.site.register(Event, EventAdmin) diff --git a/manage_events/api/urls.py b/manage_events/api/urls.py index f9c3e7d..5b63eb7 100644 --- a/manage_events/api/urls.py +++ b/manage_events/api/urls.py @@ -117,4 +117,10 @@ urlpatterns = [ views.CaptureEventViewAPIView.as_view(), name="capture_event_view", ), + # For counting event shares + path( + "event//share/", + views.EventShareView.as_view(), + name="capture_event_share", + ), ] diff --git a/manage_events/api/views.py b/manage_events/api/views.py index 08f3c6d..9bc35ce 100644 --- a/manage_events/api/views.py +++ b/manage_events/api/views.py @@ -34,6 +34,7 @@ from manage_events.models import ( EventCategory, EventPrincipalInteraction, EventReview, + EventShare, EventView, Favorites, PrincipalPreference, @@ -890,3 +891,30 @@ class CaptureEventViewAPIView(APIView): errors="Event not found.", status=status.HTTP_400_BAD_REQUEST, ) + + +class EventShareView(APIView): + authentication_classes = [JWTAuthentication] + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + try: + event = Event.objects.get(id=pk) + except Event.DoesNotExist: + return ApiResponse.error( + message=constants.FAILURE, + errors="Event not found.", + status=status.HTTP_400_BAD_REQUEST, + ) + + # Incrementing the social media shares count + event.increment_shares() + + user = request.user # Assuming the user is authenticated + EventShare.objects.create(principal=user, event=event) + + return ApiResponse.success( + message=constants.SUCCESS, + data="Event shared successfully.", + status=status.HTTP_200_OK, + ) diff --git a/manage_events/management/commands/manager_report.py b/manage_events/management/commands/manager_report.py new file mode 100644 index 0000000..8da35e6 --- /dev/null +++ b/manage_events/management/commands/manager_report.py @@ -0,0 +1,33 @@ +from django.core.management.base import BaseCommand +from django.core.mail import EmailMessage +from django.conf import settings +from manage_events.report import ( + get_previous_month_date_range, + event_managers, + generate_event_report, + generate_event_report_pdf_three, +) + + +class Command(BaseCommand): + help = "Send monthly event reports to event managers" + + def handle(self, *args, **kwargs): + start_date, end_date = get_previous_month_date_range() + users = event_managers() + + for user in users: + report_data = generate_event_report(user.id) + if report_data: + pdf_data, filename = generate_event_report_pdf_three(user, report_data) + self.send_email_with_attachment(user.email, pdf_data, filename) + + def send_email_with_attachment(self, email, pdf_data, filename): + email_message = EmailMessage( + subject="Monthly Event Report", + body="Please find the attached report for the last month.", + to=[email], + from_email=settings.EMAIL_HOST_USER, + ) + email_message.attach(filename, pdf_data, "application/pdf") + email_message.send() diff --git a/manage_events/migrations/0010_event_social_media_shares_count_eventshare.py b/manage_events/migrations/0010_event_social_media_shares_count_eventshare.py new file mode 100644 index 0000000..4c6c7a4 --- /dev/null +++ b/manage_events/migrations/0010_event_social_media_shares_count_eventshare.py @@ -0,0 +1,78 @@ +# Generated by Django 5.0.2 on 2024-06-01 15:06 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_events", "0009_eventview"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="event", + name="social_media_shares_count", + field=models.IntegerField(default=0), + ), + migrations.CreateModel( + name="EventShare", + 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)), + ( + "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="social_media_shares", + 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_shares", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/manage_events/models.py b/manage_events/models.py index 1b4c55e..a961ab5 100644 --- a/manage_events/models.py +++ b/manage_events/models.py @@ -84,6 +84,11 @@ class Event(BaseModel): tags = TaggableManager(blank=True) age_group = models.CharField(max_length=100, blank=True, null=True) draft = models.BooleanField(default=False) + social_media_shares_count = models.IntegerField(default=0) + + def increment_shares(self): + self.social_media_shares_count += 1 + self.save() def __str__(self): return self.title @@ -187,3 +192,12 @@ class EventView(BaseModel): def __str__(self): return f"{self.principal.email} viewed {self.event.title} from {self.location}" + + +class EventShare(BaseModel): + event = models.ForeignKey( + Event, on_delete=models.CASCADE, related_name="social_media_shares" + ) + principal = models.ForeignKey( + IAmPrincipal, on_delete=models.CASCADE, related_name="event_shares" + ) diff --git a/manage_events/report.py b/manage_events/report.py index f435fe5..19619f5 100644 --- a/manage_events/report.py +++ b/manage_events/report.py @@ -5,13 +5,25 @@ from datetime import timedelta from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.graphics.shapes import Drawing +from reportlab.lib import colors +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.graphics.charts.piecharts import Pie +from reportlab.platypus import ( + SimpleDocTemplate, + Table, + TableStyle, + Paragraph, + Spacer, + PageBreak, +) from io import BytesIO from django.conf import settings +from collections import defaultdict from reportlab.graphics import renderPDF from django.contrib.auth import get_user_model +from accounts.models import IAmPrincipalType from goodtimes.services import EmailService -from manage_events.models import Event, EventInteractionType +from manage_events.models import Event, EventInteractionType, EventShare, EventView User = get_user_model() @@ -26,6 +38,10 @@ def generate_filename(email, date): filename = f"{username}_{month_name}_report.pdf" return filename +def event_managers(): + principal_type = IAmPrincipalType.objects.filter(name="event_manager").first() + return User.objects.filter(principal_type=principal_type, is_active=True) + def get_previous_month_date_range(): today = timezone.now() @@ -36,15 +52,11 @@ def get_previous_month_date_range(): def generate_event_report(user_id): - # Calculate the start and end dates for the previous month start_date, end_date = get_previous_month_date_range() - - # Get the user (manager) user = User.objects.get(id=user_id) - # Filter events created by the user in the previous month events = Event.objects.filter( - created_by=user, created_on__gte=start_date, created_on__lte=end_date + created_by=user, start_date__gte=start_date, start_date__lte=end_date ).annotate( favorites_count=Count( "favorites", filter=Q(favorites__active=True, favorites__deleted=False) @@ -60,154 +72,238 @@ def generate_event_report(user_id): reviews_count=Count( "reviews", filter=Q(reviews__active=True, reviews__deleted=False) ), + views_count=Count("views", filter=Q(views__active=True, views__deleted=False)), ) + # print("events: ", events) - # Generate the report report_data = [] for event in events: + views = EventView.objects.filter(event=event) + locations = defaultdict(int) + for view in views: + locations[view.location] += 1 + + shares = ( + EventShare.objects.filter(event=event) + .values("principal") + .annotate(share_count=Count("principal")) + ) + shares_data = { + User.objects.get(id=share["principal"]).get_full_name(): share[ + "share_count" + ] + for share in shares + } + report_data.append( { "event_name": event.title, + "event_type": event.category.title, + "event_date": str(event.start_date), "favorites_count": event.favorites_count, "interested_count": event.interested_count, "going_count": event.going_count, "reviews_count": event.reviews_count, + "views_count": event.views_count, + "locations": dict(locations), + "social_media_shares": event.social_media_shares_count, + "shares_data": shares_data, } ) - + # print("report_data: ", report_data) return report_data -def generate_event_report_pdf(user, report_data): +def generate_event_report_pdf_three(user, report_data): start_date, _ = get_previous_month_date_range() filename = generate_filename(user.email, start_date) buffer = BytesIO() - pdf = canvas.Canvas(buffer, pagesize=letter) + # pdf = canvas.Canvas(buffer, pagesize=letter) + pdf = SimpleDocTemplate(buffer, pagesize=letter) width, height = letter + elements = [] - # Add a title and user information - pdf.setFont("Helvetica-Bold", 16) - pdf.drawString(100, 750, "Event Report - April 2024") + styles = getSampleStyleSheet() - user_name = user.email.split("@")[0] # Use part of email before @ as username - pdf.setFont("Helvetica", 12) - pdf.drawString(100, 730, f"For Event Manager: {user_name}") + custom_style = ParagraphStyle( + name="Custom", + parent=styles["Normal"], + fontName="Helvetica", + fontSize=14, + leading=18, + spaceAfter=12, + ) + # Header Section + title = Paragraph("Good Times Ltd. Monthly Report", styles["Title"]) + report_for_month = Paragraph( + f"Report for the month of - {start_date.strftime('%B %Y')}", styles["Title"] + ) + organiser_name = Paragraph( + f"Name of the Organiser - {user.get_full_name()}", styles["Title"] + ) + contact_name = Paragraph(f"Contact Name - {user.get_full_name()}", styles["Title"]) - # Add a table header - pdf.setFont("Helvetica-Bold", 10) - pdf.drawString(50, 700, "Event Name") - pdf.drawString(250, 700, "Favorites") - pdf.drawString(350, 700, "Interested") - pdf.drawString(450, 700, "Going") - pdf.drawString(550, 700, "Reviews") - - # Loop through data and add table rows - y_pos = 680 - for event in report_data: - pdf.drawString(50, y_pos, event["event_name"]) - pdf.drawString(250, y_pos, str(event["favorites_count"])) - pdf.drawString(350, y_pos, str(event["interested_count"])) - pdf.drawString(450, y_pos, str(event["going_count"])) - pdf.drawString(550, y_pos, str(event["reviews_count"])) - y_pos -= 15 # Adjust position for next row - - # Draw the pie chart - if report_data: - pie_data = [ - sum(event[key] for event in report_data) - for key in ("favorites_count", "interested_count", "going_count") + elements.extend( + [ + title, + Spacer(1, 12), + report_for_month, + Spacer(1, 12), + organiser_name, + Spacer(1, 12), + contact_name, + PageBreak(), ] - pie_labels = ["Favorites", "Interested", "Going"] + ) - drawing = Drawing(width, height) - pie = Pie() - pie.x = 150 - pie.y = 200 - pie.width = 300 - pie.height = 150 - pie.data = pie_data - pie.labels = pie_labels - pie.slices.strokeWidth = 0.5 + # Summary Section + summary_text = ( + f"Number of Events added in {start_date.strftime('%B %Y')} - {len(report_data)}" + ) + elements.append(Paragraph(summary_text, styles["Title"])) + elements.append(Spacer(1, 24)) - drawing.add(pie) - renderPDF.draw(drawing, pdf, 150, 200) - - # Close the PDF object and write the buffer content to the PDF file - pdf.save() - buffer.seek(0) - pdf_data = buffer.read() - buffer.close() - return pdf_data, filename - - -def generate_event_report_pdf_two(user, report_data): - start_date, _ = get_previous_month_date_range() - filename = generate_filename(user.email, start_date) - - buffer = BytesIO() - pdf = canvas.Canvas(buffer, pagesize=letter) - width, height = letter - - # Add a title and user information - pdf.setFont("Helvetica-Bold", 16) - pdf.drawString(100, 750, "Event Report - April 2024") - - user_name = user.email.split("@")[0] # Use part of email before @ as username - pdf.setFont("Helvetica", 12) - pdf.drawString(100, 730, f"For Event Manager: {user_name}") - - # Event loop with row handling - y_pos = 650 # Starting position for charts (adjust as needed) - chart_width = 250 # Width of each pie chart - chart_height = 150 # Height of each pie chart - chart_spacing = 50 # Spacing between charts in a row - - for i, event in enumerate(report_data): - # Add event name as a header - pdf.setFont("Helvetica-Bold", 12) - pdf.drawString(50, y_pos + 20, event["event_name"]) - - # Check if this is the first event in a row - if i % 2 == 0: - x_pos = 75 # Starting position for charts in the first column - else: - x_pos = ( - width - chart_width - 75 - ) # Starting position for charts in the second column - - # Draw pie charts for the event - for key, value in [ - ("favorites_count", "Favorites"), - ("interested_count", "Interested"), - ("going_count", "Going"), - ]: - pie_data = [value] - pie_labels = [value] - - drawing = Drawing(chart_width, chart_height) - pie = Pie() - pie.x = 0 - pie.y = 0 - pie.width = chart_width - pie.height = chart_height - pie.data = pie_data - pie.labels = pie_labels - pie.slices.strokeWidth = 0.5 - - drawing.add(pie) - renderPDF.draw(drawing, pdf, x_pos, y_pos) - - x_pos += chart_width + chart_spacing # Adjust x position for the next chart - - y_pos -= ( - 100 # Adjust y position for the next event (adjust based on chart heights) + data = [["Sr No.", "Name of the Event", "Event Type", "Date"]] + for idx, event in enumerate(report_data, start=1): + data.append( + [ + idx, + event["event_name"], + event["event_type"], + event["event_date"], + ] ) - # Close the PDF object and write the buffer content to the PDF file - pdf.save() + table = Table(data, colWidths=[50, 200, 150, 100]) + style = TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.grey), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("BOTTOMPADDING", (0, 0), (-1, 0), 12), + ("BACKGROUND", (0, 1), (-1, -1), colors.beige), + ("GRID", (0, 0), (-1, -1), 1, colors.black), + ] + ) + table.setStyle(style) + + elements.append(table) + elements.append(PageBreak()) + + # Traffic Details Section + traffic_header = "Traffic Details for profile - Event Organisers London Ltd." + elements.append(Paragraph(traffic_header, styles["Heading2"])) + elements.append(Spacer(1, 12)) + + views_count = sum(event["views_count"] for event in report_data) + favorites_count = sum(event["favorites_count"] for event in report_data) + social_media_shares = sum(event["social_media_shares"] for event in report_data) + + elements.append( + Paragraph(f"Number of Event Views - {views_count}", styles["Normal"]) + ) + elements.append(Spacer(1, 12)) + elements.append( + Paragraph(f"Number of Event Favorites - {favorites_count}", styles["Normal"]) + ) + elements.append(Spacer(1, 12)) + elements.append( + Paragraph(f"Social Media Shares - {social_media_shares}", styles["Normal"]) + ) + # elements.append(PageBreak()) + elements.append(Spacer(1, 60)) + + # Top 5 Locations and Top 5 Viewed Events + all_locations = defaultdict(int) + print("all_locations: ", all_locations) + for event in report_data: + for location, count in event["locations"].items(): + all_locations[location] += count + + top_locations = sorted(all_locations.items(), key=lambda x: x[1], reverse=True)[:5] + print("top_locations: ", top_locations) + top_events = sorted(report_data, key=lambda x: x["views_count"], reverse=True)[:5] + print("top_events: ", top_events) + + data = [ + ["Top 5 Locations Viewed From", "Top 5 Viewed Events"], + ] + + for i in range(5): + location = top_locations[i][0] if i < len(top_locations) else "" + event = top_events[i]["event_name"] if i < len(top_events) else "" + data.append([location, event]) + + table = Table(data, colWidths=[200, 200]) + style = TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, 0), colors.grey), + ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), + ("BOTTOMPADDING", (0, 0), (-1, 0), 12), + ("BACKGROUND", (0, 1), (-1, -1), colors.beige), + ("GRID", (0, 0), (-1, -1), 1, colors.black), + ] + ) + table.setStyle(style) + + elements.append(table) + elements.append(PageBreak()) + + # Detailed Review of Each Event + for event in report_data: + elements.append(Paragraph(f"Event Name - {event['event_name']}", styles['Heading1'])) + elements.append(Spacer(1, 12)) + elements.append(Paragraph(f"Event Type - {event['event_type']}", custom_style)) + elements.append(Paragraph(f"Event Date - {event['event_date']}", custom_style)) + + views = f"Number of Views - {event['views_count']}" + favorites = f"Favorites Count - {event['favorites_count']}" + interested = f"Interested in Going - {event['interested_count']}" + going = f"Going - {event['going_count']}" + reviews = f"Reviews - {event['reviews_count']}" + shares = f"Social Media Shares - {event['social_media_shares']}" + + elements.append(Paragraph(views, custom_style)) + elements.append(Paragraph(shares, custom_style)) + elements.append(Paragraph(favorites, custom_style)) + elements.append(Paragraph(interested, custom_style)) + elements.append(Paragraph(going, custom_style)) + elements.append(Paragraph(reviews, custom_style)) + elements.append(Spacer(1, 12)) + + location_details = [] + for location, count in event["locations"].items(): + location_details.append(f"{location}: {count}") + + elements.append(Paragraph("Event viewed from:", custom_style)) + elements.append(Paragraph(", ".join(location_details), custom_style)) + # elements.append(PageBreak()) + elements.append(Spacer(1, 48)) + + pie_data = [ + event["views_count"], + event["social_media_shares"], + event["favorites_count"], + event["interested_count"], + event["going_count"], + ] + pie_labels = ["Views", "Shares", "Favorites", "Interested", "Going"] + + drawing = Drawing(200, 100) + pie = Pie() + pie.data = pie_data + pie.labels = pie_labels + pie.width = 100 + pie.height = 100 + drawing.add(pie) + elements.append(drawing) + elements.append(PageBreak()) + pdf.build(elements) buffer.seek(0) pdf_data = buffer.read() buffer.close() - return pdf_data, filename diff --git a/manage_events/views.py b/manage_events/views.py index 05c53e5..0bfcc9e 100644 --- a/manage_events/views.py +++ b/manage_events/views.py @@ -481,7 +481,7 @@ class VenueDeleteView(LoginRequiredMixin, generic.View): User = get_user_model() -from .report import generate_event_report, generate_event_report_pdf +from .report import generate_event_report, generate_event_report_pdf_three from django.http import HttpResponse @@ -495,10 +495,10 @@ class GenerateEventReportView(generic.View): user = get_object_or_404(User, id=user_id) # Generate the PDF - pdf_data, filename = generate_event_report_pdf(user, report_data) + pdf_data, filename = generate_event_report_pdf_three(user, report_data) # Create the HttpResponse object with the PDF data response = HttpResponse(pdf_data, content_type="application/pdf") response["Content-Disposition"] = f'attachment; filename="{filename}"' - + return response diff --git a/requirements.txt b/requirements.txt index 094748b..e80e07b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -68,7 +68,7 @@ sqlparse==0.4.4 stripe==8.2.0 tqdm==4.66.2 Twisted==23.10.0 -twisted-iocpsupport==1.0.4 +# twisted-iocpsupport==1.0.4 txaio==23.1.1 typing_extensions==4.9.0 tzdata==2024.1