Skip to content

Commit 11a5af0

Browse files
committed
Allow setting room sizes from the room
1 parent 2073cea commit 11a5af0

7 files changed

Lines changed: 118 additions & 18 deletions

File tree

backend/api/schedule/mutations/book_schedule_item.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ def book_schedule_item(info: Info, id: strawberry.ID) -> BookScheduleItemResult:
5656
):
5757
return UserNeedsConferenceTicket()
5858

59-
if schedule_item.attendees_total_capacity is None:
59+
if schedule_item.actual_attendees_total_capacity is None:
6060
return ScheduleItemNotBookable()
6161

6262
if schedule_item.attendees.filter(user_id=user_id).exists():
6363
return UserIsAlreadyBooked()
6464

65-
if schedule_item.attendees.count() >= schedule_item.attendees_total_capacity:
65+
if schedule_item.attendees.count() >= schedule_item.actual_attendees_total_capacity:
6666
return ScheduleItemIsFull()
6767

6868
ScheduleItemAttendee.objects.create(schedule_item=schedule_item, user_id=user_id)

backend/api/schedule/mutations/cancel_booking_schedule_item.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def cancel_booking_schedule_item(
3030
schedule_item = ScheduleItem.objects.get(id=id)
3131
user_id = info.context.request.user.id
3232

33-
if schedule_item.attendees_total_capacity is None:
33+
if schedule_item.actual_attendees_total_capacity is None:
3434
return ScheduleItemNotBookable()
3535

3636
if not schedule_item.attendees.filter(user_id=user_id).exists():

backend/api/schedule/tests/test_book_spot_schedule_item.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from schedule.tests.factories import (
44
DayFactory,
5+
RoomFactory,
56
ScheduleItemAttendeeFactory,
67
ScheduleItemFactory,
78
SlotFactory,
@@ -186,3 +187,69 @@ def test_user_cannot_book_any_event(graphql_client, user, simple_schedule_item,
186187
assert not ScheduleItemAttendee.objects.filter(
187188
schedule_item=schedule_item, user_id=user.id
188189
).exists()
190+
191+
192+
def test_user_book_with_limited_capacity_from_room(
193+
graphql_client, user, simple_schedule_item, mocker
194+
):
195+
mocker.patch(
196+
"api.schedule.mutations.book_schedule_item.user_has_admission_ticket",
197+
return_value=True,
198+
)
199+
200+
graphql_client.force_login(user)
201+
202+
schedule_item = simple_schedule_item
203+
schedule_item.rooms.add(RoomFactory(attendees_total_capacity=10))
204+
schedule_item.attendees_total_capacity = None
205+
schedule_item.save()
206+
207+
ScheduleItemAttendeeFactory.create_batch(2, schedule_item=schedule_item)
208+
209+
response = graphql_client.query(
210+
"""mutation($id: ID!) {
211+
bookScheduleItem(id: $id) {
212+
__typename
213+
}
214+
}""",
215+
variables={"id": schedule_item.id},
216+
)
217+
218+
assert response["data"]["bookScheduleItem"]["__typename"] == "ScheduleItem"
219+
220+
assert ScheduleItemAttendee.objects.filter(
221+
schedule_item=schedule_item, user_id=user.id
222+
).exists()
223+
224+
225+
def test_user_book_full_item_from_room(
226+
graphql_client, user, simple_schedule_item, mocker
227+
):
228+
mocker.patch(
229+
"api.schedule.mutations.book_schedule_item.user_has_admission_ticket",
230+
return_value=True,
231+
)
232+
233+
graphql_client.force_login(user)
234+
235+
schedule_item = simple_schedule_item
236+
schedule_item.rooms.add(RoomFactory(attendees_total_capacity=10))
237+
schedule_item.attendees_total_capacity = None
238+
schedule_item.save()
239+
240+
ScheduleItemAttendeeFactory.create_batch(11, schedule_item=schedule_item)
241+
242+
response = graphql_client.query(
243+
"""mutation($id: ID!) {
244+
bookScheduleItem(id: $id) {
245+
__typename
246+
}
247+
}""",
248+
variables={"id": schedule_item.id},
249+
)
250+
251+
assert response["data"]["bookScheduleItem"]["__typename"] == "ScheduleItemIsFull"
252+
253+
assert not ScheduleItemAttendee.objects.filter(
254+
schedule_item=schedule_item, user_id=user.id
255+
).exists()

backend/api/schedule/types/schedule_item.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class ScheduleItem:
3030
duration: int | None
3131
highlight_color: str | None
3232
language: Language
33-
audience_level: Annotated[
34-
"AudienceLevel", strawberry.lazy("api.conferences.types")
35-
] | None
33+
audience_level: (
34+
Annotated["AudienceLevel", strawberry.lazy("api.conferences.types")] | None
35+
)
3636
youtube_video_id: str | None
3737
link_to: str
3838

@@ -45,21 +45,21 @@ class ScheduleItem:
4545

4646
@strawberry.field
4747
def has_limited_capacity(self) -> bool:
48-
return self.attendees_total_capacity is not None
48+
return self.actual_attendees_total_capacity is not None
4949

5050
@strawberry.field
5151
def has_spaces_left(self) -> bool:
52-
if self.attendees_total_capacity is None:
52+
if self.actual_attendees_total_capacity is None:
5353
return True
5454

55-
return self.attendees_total_capacity - self.attendees.count() > 0
55+
return self.actual_attendees_total_capacity - self.attendees.count() > 0
5656

5757
@strawberry.field
5858
def spaces_left(self) -> int:
59-
if self.attendees_total_capacity is None:
59+
if self.actual_attendees_total_capacity is None:
6060
return 0
6161

62-
return self.attendees_total_capacity - self.attendees.count()
62+
return self.actual_attendees_total_capacity - self.attendees.count()
6363

6464
@strawberry.field
6565
def user_has_spot(self, info) -> bool:
@@ -124,7 +124,7 @@ def image(self, info) -> str | None:
124124

125125
return info.context.request.build_absolute_uri(self.image.url)
126126

127-
@strawberry.field(name='slidoUrl')
127+
@strawberry.field(name="slidoUrl")
128128
def _slido_url(self, info) -> str:
129129
if self.slido_url:
130130
return self.slido_url

backend/schedule/admin.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ class ScheduleItemAdmin(ConferencePermissionMixin, admin.ModelAdmin):
341341
"link_to",
342342
"slido_url",
343343
"talk_manager",
344-
'livestreaming_room',
344+
"livestreaming_room",
345345
)
346346
},
347347
),
@@ -420,9 +420,9 @@ def export_attendees(self, request, object_id: int):
420420
export_data = csv_format.export_data(data)
421421
date_str = timezone.now().strftime("%Y-%m-%d")
422422
response = HttpResponse(export_data, content_type=csv_format.get_content_type())
423-
response[
424-
"Content-Disposition"
425-
] = f'attachment; filename="{schedule_item.slug}-attendees-{date_str}.csv"'
423+
response["Content-Disposition"] = (
424+
f'attachment; filename="{schedule_item.slug}-attendees-{date_str}.csv"'
425+
)
426426
return response
427427

428428
def email_speakers(self, request):
@@ -482,10 +482,10 @@ def email_speakers(self, request):
482482
return TemplateResponse(request, "email-speakers.html", context)
483483

484484
def spaces_left(self, obj):
485-
if obj.attendees_total_capacity is None:
485+
if obj.actual_attendees_total_capacity is None:
486486
return None
487487

488-
return obj.attendees_total_capacity - obj.attendees.count()
488+
return obj.actual_attendees_total_capacity - obj.attendees.count()
489489

490490
def save_form(self, request, form, change):
491491
if form.cleaned_data["new_slot"]:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.8 on 2026-03-03 12:52
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('schedule', '0056_scheduleitem_livestreaming_room'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='room',
15+
name='attendees_total_capacity',
16+
field=models.PositiveIntegerField(blank=True, help_text='Maximum capacity for this room. Leave blank to not limit attendees.', null=True, verbose_name='default attendees total capacity'),
17+
),
18+
]

backend/schedule/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ class Room(models.Model):
3131

3232
name = models.CharField(_("name"), max_length=100)
3333
type = models.CharField(_("type"), choices=TYPES, max_length=10, default=TYPES.talk)
34+
attendees_total_capacity = models.PositiveIntegerField(
35+
_("default attendees total capacity"),
36+
null=True,
37+
blank=True,
38+
help_text=_(
39+
"Maximum capacity for this room. Leave blank to not limit attendees."
40+
),
41+
)
3442

3543
def __str__(self):
3644
return self.name
@@ -318,6 +326,13 @@ class ScheduleItem(TimeStampedModel):
318326

319327
objects = ScheduleItemQuerySet().as_manager()
320328

329+
@cached_property
330+
def actual_attendees_total_capacity(self):
331+
if self.attendees_total_capacity:
332+
return self.attendees_total_capacity
333+
rooms = self.rooms.all()
334+
return rooms[0].attendees_total_capacity if rooms else None
335+
321336
@cached_property
322337
def speakers(self):
323338
speakers = []

0 commit comments

Comments
 (0)