Skip to content

Commit

Permalink
Merge pull request #147 from ECE444-2023Fall/fabin-dev-analytics
Browse files Browse the repository at this point in the history
Add analytics for admin events page
  • Loading branch information
pandyah5 authored Nov 11, 2023
2 parents c3564af + 16bc7cf commit dc96ed8
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 92 deletions.
5 changes: 4 additions & 1 deletion app/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from app.auth import organizer_required
from app.database import Credentials, EventRegistration, EventDetails, EventBanner, EventRating
from app.forms import EventCreateForm
from app.organizer import get_user_analytics

events = Blueprint("events", __name__)

Expand Down Expand Up @@ -98,12 +99,14 @@ def show_event_admin(id):
registered_users = EventRegistration.query.filter_by(event_id=id).all()
num_of_registrations = len(registered_users)

user_analytic_charts = get_user_analytics(event_id=id)

# Fix: Need to pass event ID as a string
event_dict = event.__dict__
str_id = str(event_dict["id"])
event_dict["id"] = str_id

return render_template("event_admin.html", event=event_dict, num_of_registrations=num_of_registrations)
return render_template("event_admin.html", event=event_dict, num_of_registrations=num_of_registrations, user_analytic_charts=user_analytic_charts)


@events.route("/events/create_event", methods=["GET", "POST"])
Expand Down
130 changes: 55 additions & 75 deletions app/organizer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from flask import Blueprint, render_template
from flask_login import login_required, current_user
from sqlalchemy import func
import matplotlib.pyplot as plt
from io import BytesIO
import base64
import logging

from app.main import db # db is for database
from app.database import EventDetails, EventBanner, Credentials
from app.database import EventDetails, Credentials, UserDetails, EventRegistration
from app.auth import organizer_required

organizer = Blueprint("organizer", __name__)
Expand All @@ -19,77 +24,52 @@ def main():
)
return render_template("organizer_main.html", my_events_data=my_events_data)


@organizer.route("/organizer/create_event", methods=["GET", "POST"])
@login_required
@organizer_required
def create_event():
form = EventCreateForm()

if form.validate_on_submit():
# Add the event details
new_event = EventDetails(
name=form.name.data,
short_description=form.short_description.data,
long_description=form.long_description.data,
category=form.category.data.lower(),
is_online=form.is_online.data,
venue=form.venue.data,
start_date=form.start_date.data,
end_date=form.end_date.data,
start_time=form.start_time.data,
end_time=form.end_time.data,
max_capacity=form.max_capacity.data,
current_capacity=0,
ticket_price=form.ticket_price.data,
redirect_link=form.redirect_link.data,
additional_info=form.additional_info.data,
)
db.session.add(new_event)
db.session.commit()

# Add the event details to the index for elastic search
add_event_to_index(new_event)

# Add the banner information for the newly created event
banner_file = form.banner_image.data
filename = "event_banner_" + str(new_event.id) + ".png"

# Save the banner in static/event-assets
banner_file.save(
os.path.join(current_app.root_path, "static", "event-assets", filename)
)

# Store the path to the banner EventBanner
event_graphic = EventBanner(event_id=new_event.id, image=filename)

db.session.add(event_graphic)
db.session.commit()

new_organizer_event_relation = OrganizerEventDetails(
event_id=new_event.id, organizer_username=current_user.username
)
db.session.add(new_organizer_event_relation)
db.session.commit()

return redirect(url_for("organizer.main"))

organizer = current_user

return render_template("create_event.html", form=form)

#Adds it to the "index" which we use for searching events
def add_event_to_index(new_event):
event_detail = {
"id": new_event.id,
"name": new_event.name,
"description": new_event.description,
"category": new_event.category,
"venue": new_event.venue,
"additional_info": new_event.additional_info,
}

logging.info("The event dict for indexing:", event_detail)
es.index(index="events", document=event_detail)
es.indices.refresh(index="events")
logging.info(es.cat.count(index="events", format="json"))
def get_user_analytics(event_id):
# Define a function that generates an analytic chart
# This function takes in an analytic column as an input (ie. Department), and
# returns a pie chart (with a title and legend)
# NOTE: If the function does not find any analytics data then it returns None
def get_analytic_chart_given_column(event_id, user_detail_column, title):
analytics_data = db.session.query(user_detail_column, func.count(UserDetails.username).label('order_count')).\
join(EventRegistration, UserDetails.username == EventRegistration.attendee_username).\
filter(EventRegistration.event_id == event_id).group_by(user_detail_column).all()

# No analytics data found
if len(analytics_data) == 0:
logging.info(f"Column ({title}) has no analytics data")
return None

logging.info(f"Analytics for {title}: {analytics_data}")

# Create a matplotlib plot
plt.figure(figsize=(8, 8))
keys = [elem[0] for elem in analytics_data]
values = [elem[1] for elem in analytics_data]
colors = plt.cm.Paired(range(len(keys)))
wedges, _ = plt.pie(values, autopct=None, startangle=90, colors=colors)
plt.title(f"{title} Distribution")

plt.legend(wedges, keys, title=f'{title} Categories', loc='upper center', bbox_to_anchor=(1, 1))
plt.axis('equal')

# Save the chart to a BytesIO object
chart_image = BytesIO()
plt.savefig(chart_image, format='png', bbox_inches='tight')
chart_image.seek(0)

# Encode the image to base64 for embedding in HTML
chart_image_base64 = base64.b64encode(chart_image.read()).decode('utf-8')
return chart_image_base64

analytics_charts = []

# Define the different interested analytics
titles = ["Years", "Campus", "Department", "Course Type"]
columns = [UserDetails.year, UserDetails.campus, UserDetails.department, UserDetails.course_type]
for title, column in zip(titles, columns):
chart = get_analytic_chart_given_column(event_id, column, title)
if chart is None:
continue
analytics_charts.append(chart)

return analytics_charts
19 changes: 19 additions & 0 deletions app/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,25 @@ h1.heading {
border-radius: 20px;
}

.user-analytics-container {
padding: 0vw;
}

.user-analytics-container .row {
padding: 0vw;
align-items: start;
}

.user-analytics-object {
display: block;
padding: 2vw;
}

.user-analytics-object img{
width: 100%;
object-fit:cover;
transition: all .25s ease;

/* The style below is for Error Pages */
div.error-container {
text-align: center;
Expand Down
103 changes: 87 additions & 16 deletions app/templates/event_admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,95 @@ <h3 class="nav-logo"> UofT Event<span class="hub">Hub</span> </h3>
{% endwith %}

{% block page_content %}
<div style="display: flex;">
<form action="/events/edit_event/{{event.id}}" method="GET">
<button type="submit" class="btn btn-primary" name="update-action" value="update">Edit Event</button>
</form>
<form action="/events/delete_event/{{event.id}}" method="POST" onsubmit="return confirmDelete();" style="margin-left: 50px;">
<button type="submit" class="btn btn-danger" name="update-action" value="delete">Delete Event</button>
</form>
</div>
<div class="page-header">
<h1>{{event.name}}</h1>
<h3>{{event.short_description}}</h3>
<p>{{event.long_description}}</p>
<p>Venue: {{event.venue}}</p>
<p>No of Registrations: {{num_of_registrations}} / {{event.max_capacity}}</p>
<!-- Creates a structured view of an event, displaying its name, description, venue, and an image banner related to the event, -->
<div class="row">
<div class="page-header">
<div style="text-align: center;">
<img src="{{ url_for ('static', filename='event-assets/event_banner_' + event.id + '.png')}}" class="event-banner" alt="Event Banner">
</div>
</div>
</div>
<div>
<img src="{{ url_for ('static', filename='event-assets/event_banner_' + event.id + '.png')}}" class="img-rounded" alt="Event Banner">
<div class="row">
<div>
<p style="font-weight: 500; font-size: 1.5em;">{{event.start_date}}</p>
<h1>{{event.name}}</h1>
<p>{{event.short_description}}</p>

<div class="event-section">
<h4 class="event-section-header">Number of Registrations</h4>
<p>{{num_of_registrations}} <b>/ {{event.max_capacity}}</b></p>
</div>

<div class="event-section">
<h4 class="event-section-header">Date and Time</h4>
<p>Starts on: {{event.start_date}}</p>
<p>Ends on: {{event.end_date}}</p>
<p>From: {{event.start_time}} to {{event.end_time}} EST</p>
</div>

<div class="event-section">
<h4 class="event-section-header">Location</h4>
<p>Venue: {{event.venue}}</p>
</div>

<div class="event-section">
<h4 class="event-section-header">About this event</h4>
{% if event.additional_info != ""%}
<p>{{event.long_description}}</p>
{% else %}
<p>The organizer did not provide detailed information about the event.</p>
{% endif %}
</div>

<div class="event-section">
<h4 class="event-section-header">Ticket Price</h4>
{% if event.ticket_price == 0.0 %}
<p>Tickets cost: Free</p>
{% else %}
<p>Tickets cost (per person): {{event.ticket_price}} CAD</p>
{% endif %}
</div>

<div class="event-section">
<h4 class="event-section-header">Location</h4>
<p>Venue: {{event.venue}}</p>
</div>

{% if event.additional_info != "" %}
<div class="event-section">
<h4 class="event-section-header">Additional Information</h4>
<p>Venue: {{event.additional_info}}</p>
</div>
{% endif %}

<div class="event-section" style="display: flex;">
<form action="/events/edit_event/{{event.id}}" method="GET">
<button type="submit" class="btn btn-primary" name="update-action" value="update">Edit Event</button>
</form>
<form action="/events/delete_event/{{event.id}}" method="POST" onsubmit="return confirmDelete();" style="margin-left: 50px;">
<button type="submit" class="btn btn-danger" name="update-action" value="delete">Delete Event</button>
</form>
</div>

{% if user_analytic_charts.length != 0 %}
<div class="event-section" style="margin-top: 2vw;">
<h2 class="event-section-header">User Analytics</h2>
<div class="container user-analytics-container">
<div class="row">
{% for chart in user_analytic_charts %}
<div class="col-md-12 col-lg-6">
<div class="user-analytics-object">
<img src="data:image/png;base64,{{ chart }}" alt="User Analytic Pie Chart">
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>

<script>
function confirmDelete() {
return confirm("Are you sure you want to delete this event?");
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
matplotlib==3.7.0
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==23.1
Expand Down

0 comments on commit dc96ed8

Please sign in to comment.