Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions workshopnav/events/migrations/0012_alter_event_owner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1 on 2026-05-18 03:48

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('events', '0011_merge_20260513_2203'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AlterField(
model_name='event',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_events', to=settings.AUTH_USER_MODEL),
),
]
46 changes: 30 additions & 16 deletions workshopnav/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,67 @@
from django.contrib.auth import get_user_model
from django.conf import settings


# auto-generate a unique 6-character code for each event
def generate_code():
return uuid.uuid4().hex[:6].upper()


# Event model with title, unique event code, and creation timestamp
class Event(models.Model):
title = models.CharField(max_length=255)
event_code = models.CharField(max_length=10, unique=True, default=generate_code)
created_at = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owned_events', on_delete=models.CASCADE)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="owned_events",
on_delete=models.CASCADE,
null=True,
blank=True,
)
feedback_open = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)

def __str__(self):
return f"{self.title} ({self.event_code})"


# Poll model linked to an event, with a question, creation timestamp, and active status
class Poll(models.Model):
event = models.ForeignKey(Event, related_name='polls', on_delete=models.CASCADE)
event = models.ForeignKey(Event, related_name="polls", on_delete=models.CASCADE)
question = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=False)

def __str__(self):
return f"Poll: {self.question} for Event: {self.event.title}"



# response model linked to a poll, with a response text and creation timestamp
class PollResponse(models.Model):
poll = models.ForeignKey(Poll, related_name='responses', on_delete=models.CASCADE)
option = models.ForeignKey('PollOption', related_name='responses', on_delete=models.CASCADE)
poll = models.ForeignKey(Poll, related_name="responses", on_delete=models.CASCADE)
option = models.ForeignKey(
"PollOption", related_name="responses", on_delete=models.CASCADE
)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"Response: {self.option.option_text} for Poll: {self.poll.question}"


# options model linked to a poll, with an option text and creation timestamp
class PollOption(models.Model):
poll = models.ForeignKey(Poll, related_name='options', on_delete=models.CASCADE)
poll = models.ForeignKey(Poll, related_name="options", on_delete=models.CASCADE)
option_text = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"Option: {self.option_text} for Poll: {self.poll.question}"


# Question model linked to an event, with question text, visibility, upvotes, and creation timestamp
class Question(models.Model):
event = models.ForeignKey(Event, related_name='questions', on_delete=models.CASCADE)
event = models.ForeignKey(Event, related_name="questions", on_delete=models.CASCADE)
question_text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
anonymous = models.BooleanField(default=False)
Expand All @@ -63,31 +77,31 @@ def __str__(self):

# Feedback model
class Feedback(models.Model):
#Links feedback to specific event
# Links feedback to specific event
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="feedback")
#rating score
# rating score
rating = models.IntegerField()
#optional comment field
# optional comment field
comment = models.TextField(blank=True)
#auto stores when the feedback is created
# auto stores when the feedback is created
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"Feedback for {self.event.title}"


#Email capture model
# Email capture model
class EmailCapture(models.Model):
#Links email to an event
# Links email to an event
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="emails")
#Stores user's email
# Stores user's email
email = models.EmailField()
#When email was submitted
# When email was submitted
created_at = models.DateTimeField(auto_now_add=True)

# Prevent the same email being submitted twice for the same event
class Meta:
unique_together = ("event", "email")

def __str__(self):
return self.email
return self.email
37 changes: 25 additions & 12 deletions workshopnav/events/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@
from . import views

urlpatterns = [
path('events/', views.EventListCreateView.as_view()),
path('events/<int:event_id>/', views.EventDetailView.as_view()),
path('events/join/<str:code>/', views.AttendeeEventByCodeView.as_view()),
path('events/<str:code>/', views.FacilitatorEventDetailView.as_view()),
path('events/<int:event_id>/polls/', views.PollListCreateView.as_view()),
path('polls/<int:poll_id>/', views.PollDetailView.as_view()),
path('polls/<int:poll_id>/responses/', views.PollResponseCreateView.as_view()),
path('polls/<int:poll_id>/results/', views.PollResultsView.as_view()),
path('events/<int:event_id>/questions/', views.QuestionListCreateView.as_view()),
path('questions/<int:question_id>/upvote/', views.QuestionUpvoteView.as_view()),
path("events/", views.EventListCreateView.as_view()),
path("events/<int:pk>/", views.EventDetailView.as_view()),

# attendee join flow
path("events/join/<str:code>/", views.AttendeeEventByCodeView.as_view()),

# facilitator / event code routes
path("events/<str:code>/", views.EventByCodeView.as_view()),

# polls
path("events/<int:event_id>/polls/", views.PollListCreateView.as_view()),
path("polls/<int:poll_id>/", views.PollDetailView.as_view()),
path("polls/<int:poll_id>/responses/", views.PollResponseCreateView.as_view()),
path("polls/<int:poll_id>/results/", views.PollResultsView.as_view()),

# questions
path("events/<int:event_id>/questions/", views.QuestionListCreateView.as_view()),
path("questions/<int:question_id>/upvote/", views.QuestionUpvoteView.as_view()),

# feedback
path("events/<int:event_id>/feedback/", views.FeedbackCreateView.as_view()),
path('events/<int:event_id>/open-feedback/', views.OpenFeedbackView.as_view()),
path("events/<int:event_id>/open-feedback/", views.OpenFeedbackView.as_view()),

# emails
path("events/<int:event_id>/emails/", views.EmailCaptureCreateView.as_view()),
]
]

Loading