-
Notifications
You must be signed in to change notification settings - Fork 0
Issue 7 Add room app #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a new Room API for a room booking system, implementing CRUD operations with custom response formats and permission-based access control. The implementation uses Django REST Framework's ModelViewSet pattern to provide standardized API endpoints.
Key Changes:
- Added Room model with fields for name, image URL, location/capacity IDs, datetime ranges, and recurrence rules
- Implemented REST API with filtering by name, location_id, and capacity_id
- Added admin-only permissions for create/update/delete operations while allowing public read access
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| server/api/room/models.py | Defines the Room model with booking-related fields and timestamp tracking |
| server/api/room/serializers.py | Creates ModelSerializer exposing all Room fields |
| server/api/room/views.py | Implements RoomViewSet with custom update/delete responses and query filtering |
| server/api/room/urls.py | Configures DRF router for room endpoints |
| server/api/urls.py | Registers room API URLs under /api/rooms/ |
| server/api/settings.py | Adds room app to INSTALLED_APPS |
| server/api/room/tests.py | Provides API tests for list, retrieve, update, and delete operations |
| server/api/room/admin.py | Registers Room model in Django admin with custom display fields |
| server/api/room/apps.py | Configures the room Django app |
| server/api/room/migrations/0001_initial.py | Initial migration creating the Room table |
| server/api/room/migrations/0002_alter_room_created_at_alter_room_updated_at.py | Updates created_at/updated_at fields to use auto timestamps |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
server/api/room/models.py
Outdated
| start_datetime = models.DateTimeField() | ||
| end_datetime = models.DateTimeField() | ||
| recurrence_rule = models.TextField() | ||
| created_at = models.DateTimeField(auto_now_add=True) | ||
| updated_at = models.DateTimeField(auto_now=True) | ||
|
|
||
| def __str__(self): | ||
| return self.name |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The model design appears conceptually flawed for a room booking system. The start_datetime, end_datetime, and recurrence_rule fields seem to represent booking/availability information rather than room properties. A Room should typically contain static properties (name, location, capacity), while booking information should be in a separate related model (e.g., RoomBooking or RoomAvailability) with a ForeignKey to Room. This would allow multiple bookings per room.
| start_datetime = models.DateTimeField() | |
| end_datetime = models.DateTimeField() | |
| recurrence_rule = models.TextField() | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| def __str__(self): | |
| return self.name | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| def __str__(self): | |
| return self.name | |
| class RoomBooking(models.Model): | |
| id = models.AutoField(primary_key=True) | |
| room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name='bookings') | |
| start_datetime = models.DateTimeField() | |
| end_datetime = models.DateTimeField() | |
| recurrence_rule = models.TextField(blank=True, null=True) | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| def __str__(self): | |
| return f"Booking for {self.room.name} from {self.start_datetime} to {self.end_datetime}" |
| return Response({ | ||
| "id": instance.id, | ||
| "name": instance.name, | ||
| "updated_at": instance.updated_at | ||
| }) |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom update response doesn't follow REST conventions. The response returns only id, name, and updated_at but omits other fields that may have been updated. This could confuse API consumers who expect to see the full updated resource or at least all modified fields. Consider returning the complete serialized object or using the standard serializer response.
| return Response({ | |
| "id": instance.id, | |
| "name": instance.name, | |
| "updated_at": instance.updated_at | |
| }) | |
| return Response(serializer.data) |
server/api/room/models.py
Outdated
|
|
||
|
|
||
| class Room(models.Model): | ||
| id = models.AutoField(primary_key=True) | ||
| name = models.TextField() | ||
| img_url = models.TextField() | ||
| location_id = models.IntegerField() | ||
| capacity_id = models.IntegerField() |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location_id and capacity_id fields use IntegerField without any validation constraints. These fields could accept negative values or zero, which likely don't make sense for IDs. Consider adding validation with PositiveIntegerField or adding validators like MinValueValidator(1) to ensure valid ID values.
| class Room(models.Model): | |
| id = models.AutoField(primary_key=True) | |
| name = models.TextField() | |
| img_url = models.TextField() | |
| location_id = models.IntegerField() | |
| capacity_id = models.IntegerField() | |
| from django.core.validators import MinValueValidator | |
| class Room(models.Model): | |
| id = models.AutoField(primary_key=True) | |
| name = models.TextField() | |
| img_url = models.TextField() | |
| location_id = models.PositiveIntegerField(validators=[MinValueValidator(1)]) | |
| capacity_id = models.PositiveIntegerField(validators=[MinValueValidator(1)]) |
server/api/room/serializers.py
Outdated
| class RoomSerializer(serializers.ModelSerializer): | ||
| class Meta: | ||
| model = Room | ||
| fields = "__all__" |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using fields = "__all__" exposes all model fields including auto-generated fields like created_at and updated_at which shouldn't typically be writable via the API. Consider explicitly listing fields or using read_only_fields = ['id', 'created_at', 'updated_at'] to prevent clients from modifying these fields.
| fields = "__all__" | |
| fields = "__all__" | |
| read_only_fields = ['id', 'created_at', 'updated_at'] |
|
|
||
|
|
||
| class Room(models.Model): | ||
| id = models.AutoField(primary_key=True) |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The id field is explicitly defined but Django automatically creates an AutoField primary key named id for models. This redundant field definition should be removed unless there's a specific reason to override the default behavior.
| id = models.AutoField(primary_key=True) |
server/api/room/tests.py
Outdated
| def test_retrieve_update_delete_room(self): | ||
| # Get a room | ||
| room = Room.objects.first() | ||
| response = self.client.get(f"/api/rooms/{room.id}/") | ||
| self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
| self.assertEqual(response.data["name"], room.name) | ||
|
|
||
| # Update room | ||
| response = self.client.patch(f"/api/rooms/{room.id}/", {"name": "Updated Room"}, format="json") | ||
| self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
| self.assertEqual(response.data["name"], "Updated Room") | ||
|
|
||
| # Check via GET | ||
| response = self.client.get(f"/api/rooms/{room.id}/") | ||
| self.assertEqual(response.data["name"], "Updated Room") | ||
|
|
||
| # Delete room | ||
| response = self.client.delete(f"/api/rooms/{room.id}/") | ||
| self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
| self.assertEqual(response.data["message"], "Room deleted successfully") | ||
|
|
||
| # Check room count | ||
| response = self.client.get("/api/rooms/") | ||
| self.assertEqual(len(response.data), 4) |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the create operation (POST). The tests verify list, retrieve, update, and delete operations, but don't test creating a new room through the API. Consider adding a test for POST requests to ensure the create functionality works correctly with admin permissions.
| qs = qs.filter(location_id=loc) | ||
|
|
||
| if cap := params.get("capacity_id"): | ||
| qs = qs.filter(capacity_id=cap) | ||
|
|
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No validation is performed on the location_id and capacity_id filter parameters. Invalid values (e.g., non-numeric strings) could cause database errors. Consider adding validation or using try-except blocks to handle ValueError when converting these parameters, or add error handling in the queryset filtering.
| qs = qs.filter(location_id=loc) | |
| if cap := params.get("capacity_id"): | |
| qs = qs.filter(capacity_id=cap) | |
| try: | |
| loc_int = int(loc) | |
| qs = qs.filter(location_id=loc_int) | |
| except (ValueError, TypeError): | |
| pass | |
| if cap := params.get("capacity_id"): | |
| try: | |
| cap_int = int(cap) | |
| qs = qs.filter(capacity_id=cap_int) | |
| except (ValueError, TypeError): | |
| pass |
server/api/room/models.py
Outdated
| start_datetime = models.DateTimeField() | ||
| end_datetime = models.DateTimeField() |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The model lacks validation to ensure end_datetime is after start_datetime. This could lead to invalid room data where the end time is before the start time. Consider adding a clean() method or model validation to enforce this constraint, or add a CheckConstraint in the model's Meta class.
| id = models.AutoField(primary_key=True) | ||
| name = models.TextField() | ||
| img_url = models.TextField() | ||
| location_id = models.IntegerField() | ||
| capacity_id = models.IntegerField() | ||
| start_datetime = models.DateTimeField() | ||
| end_datetime = models.DateTimeField() | ||
| recurrence_rule = models.TextField() | ||
| created_at = models.DateTimeField(auto_now_add=True) | ||
| updated_at = models.DateTimeField(auto_now=True) | ||
|
|
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The Room model lacks a docstring explaining what it represents and the purpose of its fields. This is especially important given the potentially confusing design where booking/availability fields are mixed with room properties. Consider adding a class docstring and field-level comments for clarity.
| id = models.AutoField(primary_key=True) | |
| name = models.TextField() | |
| img_url = models.TextField() | |
| location_id = models.IntegerField() | |
| capacity_id = models.IntegerField() | |
| start_datetime = models.DateTimeField() | |
| end_datetime = models.DateTimeField() | |
| recurrence_rule = models.TextField() | |
| created_at = models.DateTimeField(auto_now_add=True) | |
| updated_at = models.DateTimeField(auto_now=True) | |
| """ | |
| Represents a room that can be booked in the system. | |
| This model combines static room properties (such as name, image, location, and capacity) | |
| with booking/availability fields (such as start/end datetime and recurrence rule). | |
| Fields: | |
| id (AutoField): Unique identifier for the room. | |
| name (TextField): The name of the room. | |
| img_url (TextField): URL to an image representing the room. | |
| location_id (IntegerField): Identifier for the location of the room. | |
| capacity_id (IntegerField): Identifier for the room's capacity. | |
| start_datetime (DateTimeField): Start date and time for room availability or booking. | |
| end_datetime (DateTimeField): End date and time for room availability or booking. | |
| recurrence_rule (TextField): Recurrence rule for repeated bookings (e.g., weekly). | |
| created_at (DateTimeField): Timestamp when the room entry was created. | |
| updated_at (DateTimeField): Timestamp when the room entry was last updated. | |
| """ | |
| id = models.AutoField(primary_key=True) # Unique identifier for the room | |
| name = models.TextField() # The name of the room | |
| img_url = models.TextField() # URL to an image representing the room | |
| location_id = models.IntegerField() # Identifier for the location of the room | |
| capacity_id = models.IntegerField() # Identifier for the room's capacity | |
| start_datetime = models.DateTimeField() # Start date and time for room availability or booking | |
| end_datetime = models.DateTimeField() # End date and time for room availability or booking | |
| recurrence_rule = models.TextField() # Recurrence rule for repeated bookings (e.g., weekly) | |
| created_at = models.DateTimeField(auto_now_add=True) # Timestamp when the room entry was created | |
| updated_at = models.DateTimeField(auto_now=True) # Timestamp when the room entry was last updated |
server/api/room/models.py
Outdated
| name = models.TextField() | ||
| img_url = models.TextField() |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TextField is typically used for large text content. For a room name and image URL, CharField with a max_length parameter would be more appropriate. Consider using models.CharField(max_length=255) for name and models.URLField(max_length=500) for img_url to better represent the data type and add validation.
| name = models.TextField() | |
| img_url = models.TextField() | |
| name = models.CharField(max_length=255) | |
| img_url = models.URLField(max_length=500) |
|
@ErikaKK. Some considerations. I am unsure how consistent blooms booking schedule is but here are some cases to think about.
Also might be worth moving the capacity as being a field in the room model. Can handle the naming (small, medium, large etc.) on the frontend. |
|
Please rebase main |
2d52ad8 to
490d725
Compare
ErikaKK
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| class Room(models.Model): | ||
| id = models.AutoField(primary_key=True) | ||
| name = models.TextField(blank=False) | ||
| img = models.ImageField(upload_to='') # change to upload s3 later for deployment? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
img = models.ImageField(upload_to='rooms/')
| location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=False) | ||
| capacity = models.IntegerField(blank=False) | ||
| amenities = models.ManyToManyField(Amenties, blank=True) | ||
| status = models.BooleanField(default=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls add docs for this
| "updated_at": instance.updated_at | ||
| }) | ||
|
|
||
| def destroy(self, request, *args, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we don't need to delete the rooms, we'll just update the status of a room. So we won't need the delete method
| serializer.is_valid(raise_exception=True) | ||
| serializer.save() | ||
|
|
||
| return Response({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the patch method could just response "success" if it's 200, because after second thought, I don't think we need to use any of this fields after we send this request. Feel free to let me know your thoughts
|
|
||
| class Location(models.Model): | ||
| id = models.AutoField(primary_key=True) | ||
| name = models.TextField(blank=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use models.CharField(blank=False) for the string field and set set an appropriate for the field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all the TextField need to be replaced with CharField
| status = models.BooleanField(default=True) | ||
| start_datetime = models.DateTimeField(blank=False) | ||
| end_datetime = models.DateTimeField(blank=False) | ||
| recurrence_rule = models.TextField(blank=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is an optional field
| # STATIC_ROOT is where the static files get copied to when "collectstatic" is run. | ||
| STATIC_ROOT = "static_files" | ||
|
|
||
| MEDIA_ROOT = BASE_DIR / "room_images" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can remove this, I already config this for s3
|
|
||
| def get_permissions(self): | ||
| if self.action in ["create", "update", "destroy"]: | ||
| return [permissions.IsAdminUser()] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we should have permissions.IsAuthenticated() for user who has an account
| urlpatterns = [ | ||
| path("admin/", admin.site.urls), | ||
| path("api/healthcheck/", include(("api.healthcheck.urls"))), | ||
| <<<<<<< HEAD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please resolve conflict here

Change Summary
This is the room app model (very rough draft)
to test run python manage.py test
Change Form
Fill this up (NA if not available). If a certain criteria is not met, can you please give a reason.
Other Information
[Is there anything in particular in the review that I should be aware of?]
Resolves:
Issue #7