Skip to content

Commit 8a0dbaa

Browse files
committed
feat(Event-registration): added participation in Event model
1 parent f118943 commit 8a0dbaa

8 files changed

+295
-4
lines changed

event/admin.py

+78-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Register your models here.
2+
from django.db.models import F, Count
3+
from django.contrib import messages
24
from django import forms
35
from . import models
46
from core.base_admin import SummernoteModelAdmin, SummernoteInlineMixin
@@ -23,6 +25,49 @@ class EventImagesInline(SummernoteInlineMixin, NestedStackedInline):
2325
extra = 1
2426

2527

28+
class ParticipantsInline(SummernoteInlineMixin, NestedStackedInline):
29+
model = models.Participant
30+
extra = 0
31+
32+
def get_extra(self, request, obj=None, **kwargs):
33+
34+
if obj: # Ensure we're dealing with an existing Event object
35+
if obj.participants.count() < obj.max_capacity:
36+
return 1 # Show one extra form
37+
return 0 # Don't show any extra forms
38+
39+
def has_add_permission(self, request, obj=None):
40+
"""
41+
Prevent adding new participants if the event is full.
42+
"""
43+
if obj: # Ensure we're dealing with an existing Event object
44+
if obj.participants.count() >= obj.max_capacity:
45+
messages.warning(
46+
request,
47+
"The event is full. You cannot add more participants.",
48+
)
49+
return False # Hide "Add another" button
50+
return super().has_add_permission(request, obj)
51+
52+
53+
class ParticipantAdminForm(forms.ModelForm):
54+
class Meta:
55+
model = models.Participant
56+
fields = "__all__"
57+
58+
"""
59+
Overriding the __init__ method to filter the events that are checked
60+
"""
61+
62+
def __init__(self, *args, **kwargs):
63+
super().__init__(*args, **kwargs)
64+
self.fields["events"].queryset = models.Event.objects.annotate(
65+
participant_count=Count("participants")
66+
).filter(
67+
requires_registration=True, participant_count__lt=F("max_capacity")
68+
) # Ensures max_capacity > p_count
69+
70+
2671
class EventAdminForms(forms.ModelForm):
2772
class Meta:
2873
model = models.Event
@@ -33,6 +78,7 @@ class Meta:
3378
"start_date": "This is the date the event will start",
3479
"end_date": "This is the date the event will end",
3580
"description": "This is the description of the event",
81+
"requires_registration": "Check if the event needs registration",
3682
"rsvp_url": "This is the link to the RSVP page",
3783
"add_to_calender_url": "Add the event to your calendar",
3884
"is_draft": "This is the status of the event",
@@ -51,12 +97,21 @@ class EventModelAdmin(NestedModelAdmin, SummernoteModelAdmin):
5197
list_display = ["title", "is_draft", "start_date", "end_date"]
5298
search_fields = ["slug", "title"]
5399
readonly_fields = ["created_at", "updated_at", "slug"]
54-
inlines = [
55-
EventScheduleInline,
56-
EventImagesInline,
57-
]
100+
inlines = [EventScheduleInline, EventImagesInline]
58101
autocomplete_fields = ["location", "event_type", "hot_topics"]
59102

103+
"""Overriding this method to conditionally
104+
add the ParticipantsInline to the inlines list
105+
"""
106+
107+
def get_inline_instances(self, request, obj=None):
108+
inline_instances = super().get_inline_instances(request, obj)
109+
if obj and obj.requires_registration:
110+
inline_instances.append(
111+
ParticipantsInline(self.model, self.admin_site)
112+
)
113+
return inline_instances
114+
60115

61116
@admin.register(models.Speaker)
62117
class SpeakersAdmin(SummernoteInlineMixin, admin.ModelAdmin):
@@ -90,6 +145,25 @@ class HotTopicAdmin(admin.ModelAdmin):
90145
search_fields = ["name"]
91146

92147

148+
@admin.register(models.Participant)
149+
class ParticipantAdmin(admin.ModelAdmin):
150+
form = ParticipantAdminForm
151+
list_display = [
152+
"first_name",
153+
"last_name",
154+
"email",
155+
"academy",
156+
"phone_number",
157+
]
158+
search_fields = [
159+
"first_name",
160+
"last_name",
161+
"email",
162+
"academy",
163+
"phone_number",
164+
]
165+
166+
93167
admin.site.register(
94168
[
95169
models.EventImage,

event/migrations/0007_participant.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Generated by Django 5.1.1 on 2025-01-11 15:21
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("event", "0006_alter_event_image_alter_eventimage_image_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="Participant",
16+
fields=[
17+
(
18+
"id",
19+
models.BigAutoField(
20+
auto_created=True,
21+
primary_key=True,
22+
serialize=False,
23+
verbose_name="ID",
24+
),
25+
),
26+
("first_name", models.CharField(max_length=255)),
27+
("last_name", models.CharField(max_length=255)),
28+
("email", models.EmailField(max_length=255)),
29+
("academy", models.CharField(max_length=255)),
30+
("phone_number", models.CharField(max_length=255)),
31+
(
32+
"events",
33+
models.ForeignKey(
34+
on_delete=django.db.models.deletion.CASCADE,
35+
related_name="participants",
36+
to="event.event",
37+
),
38+
),
39+
],
40+
),
41+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.1 on 2025-01-11 15:58
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("event", "0007_participant"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="participant",
15+
name="phone_number",
16+
field=models.CharField(max_length=14),
17+
),
18+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 5.1.1 on 2025-01-11 16:58
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("event", "0008_alter_participant_phone_number"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="participant",
16+
name="age",
17+
field=models.IntegerField(default=20),
18+
preserve_default=False,
19+
),
20+
migrations.AlterField(
21+
model_name="participant",
22+
name="phone_number",
23+
field=models.CharField(
24+
max_length=14,
25+
validators=[
26+
django.core.validators.RegexValidator(
27+
message="Phone number must be entered in the format: '9999999999'. Up to 14 digits allowed including +977",
28+
regex="^\\+?[0-9]*$",
29+
)
30+
],
31+
),
32+
),
33+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Generated by Django 5.1.1 on 2025-01-12 06:42
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("event", "0009_participant_age_alter_participant_phone_number"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="event",
16+
name="requires_registration",
17+
field=models.BooleanField(default=False),
18+
),
19+
migrations.AlterField(
20+
model_name="participant",
21+
name="academy",
22+
field=models.CharField(blank=True, max_length=255),
23+
),
24+
migrations.AlterField(
25+
model_name="participant",
26+
name="phone_number",
27+
field=models.CharField(
28+
max_length=14,
29+
validators=[
30+
django.core.validators.RegexValidator(
31+
message=(
32+
"Phone number must be 10 digit",
33+
"long and Up to 14 digits allowed including +977",
34+
),
35+
regex="^\\+?[0-9]{10,14}$",
36+
)
37+
],
38+
),
39+
),
40+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 5.1.1 on 2025-01-12 07:05
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("event", "0010_event_requires_registration_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="participant",
16+
name="events",
17+
field=models.ForeignKey(
18+
error_messages="Event is required",
19+
on_delete=django.db.models.deletion.CASCADE,
20+
related_name="participants",
21+
to="event.event",
22+
),
23+
),
24+
]

event/models.py

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from uuid import uuid4
33

44
from django.core.exceptions import ValidationError
5+
from django.core.validators import RegexValidator
56
from django.db import models
67
from django.utils.text import slugify
78

@@ -42,6 +43,7 @@ class Event(models.Model):
4243
start_date = models.DateField(blank=False, null=False)
4344
end_date = models.DateField(blank=False, null=False)
4445
description = models.TextField(blank=False, null=False)
46+
requires_registration = models.BooleanField(default=False)
4547
rsvp_url = models.URLField(max_length=500, null=True, blank=True)
4648
image = models.ImageField(
4749
upload_to="%Y/%m/%d/events", null=True, blank=True
@@ -162,6 +164,13 @@ def clean(self):
162164
"Registration deadline must be less than start date."
163165
)
164166

167+
"""
168+
validation if the participants exceeds the maximum capacity"""
169+
if self.participants.count() > self.max_capacity:
170+
raise ValidationError(
171+
"Participants cannot be more than the maximum capacity"
172+
)
173+
165174
def save(self, *args, **kwargs):
166175
"""
167176
We are overriding events save model
@@ -246,3 +255,48 @@ def __str__(self):
246255

247256
class Meta:
248257
ordering = ["position"]
258+
259+
260+
class Participant(models.Model):
261+
first_name = models.CharField(max_length=255)
262+
last_name = models.CharField(max_length=255)
263+
email = models.EmailField(max_length=255)
264+
academy = models.CharField(max_length=255, blank=True)
265+
phone_number = models.CharField(
266+
max_length=14,
267+
validators=[
268+
RegexValidator(
269+
# makes sure the it is at least 10 and no more than 14
270+
regex=r"^\+?[0-9]{10,14}$",
271+
message=(
272+
"Phone number must be 10 digit",
273+
"long and Up to 14 digits allowed including +977",
274+
),
275+
)
276+
],
277+
)
278+
events = models.ForeignKey(
279+
Event,
280+
related_name="participants",
281+
on_delete=models.CASCADE,
282+
blank=False,
283+
null=False,
284+
)
285+
age = models.IntegerField(null=False, blank=False)
286+
287+
def clean(self):
288+
"""
289+
Do not allow saving the participant if the event is full.
290+
"""
291+
if self.events.participants.count() >= self.events.max_capacity:
292+
raise ValidationError("Event is full")
293+
if not self.events:
294+
raise ValidationError("Event is required")
295+
296+
def save(self, *args, **kwargs):
297+
# Call clean() before saving
298+
self.clean()
299+
super().save(*args, **kwargs)
300+
301+
def __str__(self):
302+
return self.first_name + " " + self.last_name

event/serializers.py

+7
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,19 @@ class Meta:
3535
fields = "__all__"
3636

3737

38+
class ParticipantsSerializer(serializers.ModelSerializer):
39+
class Meta:
40+
model = models.Participant
41+
fields = "__all__"
42+
43+
3844
class EventSerializer(serializers.ModelSerializer):
3945
event_type = serializers.StringRelatedField()
4046
location = EventLocationSerializer()
4147
hot_topics = serializers.StringRelatedField(many=True)
4248
schedules = ScheduleSerializer(many=True)
4349
images = serializers.SerializerMethodField(read_only=True)
50+
participants = ParticipantsSerializer(many=True)
4451

4552
def get_images(self, obj):
4653
"""

0 commit comments

Comments
 (0)