Skip to content
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

Add models for schedule and availability #2576

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from

Conversation

anroopak
Copy link
Contributor

@anroopak anroopak commented Nov 4, 2024

Proposed Changes

  • Added models for schedule and availability
  • Added APIs for Schedule and Schedule Exception.
  • Added Tests for Schedule and Schedule Exception APIs.

Associated Issue

  • Link to issue here, explain how the proposed solution will solve the reported issue/ feature request.

Architecture changes

  • Remove this section if not used

Merge Checklist

  • Tests added/fixed
  • Update docs in /docs
  • Linting Complete
  • Any other necessary step

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive scheduling management system, including serializers and viewsets for managing schedules and availability.
    • Added new API endpoints for managing schedules and schedule exceptions, enhancing user interaction with scheduling functionalities.
    • Implemented advanced filtering capabilities for schedules based on user roles and associated resources.
  • Bug Fixes

    • Improved error handling for schedule-related operations, ensuring proper responses for non-existent records.
  • Tests

    • Implemented a robust test suite for the scheduling API, covering various functionalities including creation, deletion, and updates of schedules and exceptions.
  • Documentation

    • Updated API routing to include new schedule-related viewsets, enhancing the overall API structure.

Copy link

codecov bot commented Nov 4, 2024

Codecov Report

Attention: Patch coverage is 83.95522% with 43 lines in your changes missing coverage. Please review.

Project coverage is 69.85%. Comparing base (d6d069e) to head (6f5cc57).
Report is 7 commits behind head on develop.

Files with missing lines Patch % Lines
care/facility/api/serializers/schedule.py 87.59% 11 Missing and 6 partials ⚠️
care/facility/api/viewsets/schedule.py 78.33% 7 Missing and 6 partials ⚠️
care/facility/svc/schedule.py 0.00% 11 Missing ⚠️
care/facility/models/schedule.py 96.36% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2576      +/-   ##
===========================================
+ Coverage    69.20%   69.85%   +0.64%     
===========================================
  Files          211      216       +5     
  Lines        11944    12234     +290     
  Branches      1208     1227      +19     
===========================================
+ Hits          8266     8546     +280     
- Misses        3302     3307       +5     
- Partials       376      381       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@vigneshhari vigneshhari marked this pull request as draft November 5, 2024 07:59
@vigneshhari vigneshhari changed the title [DRAFT] add models for schedule and availability Add models for schedule and availability Nov 5, 2024
Copy link

coderabbitai bot commented Nov 8, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive suite of files focused on managing scheduling functionalities within a facility management system. It includes serializers, viewsets, models, service functions, and tests specifically designed for schedules and schedule exceptions. New routes are added to the API router to facilitate access to these features. The implementation emphasizes CRUD operations, data validation, and permission checks, ensuring robust handling of scheduling data and related exceptions.

Changes

File Path Change Summary
care/facility/api/serializers/schedule.py Added multiple serializers for scheduling, including creation, reading, updating, and exception handling.
care/facility/api/viewsets/schedule.py Introduced ScheduleViewSet and ScheduleExceptionViewSet for managing schedules and exceptions with CRUD operations.
care/facility/models/schedule.py Defined models for scheduling resources, including SlotType, Schedule, Availability, and ScheduleException.
care/facility/svc/schedule.py Added functions for checking resource availability and retrieving current schedules and exceptions.
care/facility/tests/schedule/test_schedule_api.py Created tests for the scheduling API, covering creation, deletion, updates, and listing of schedules.
care/facility/tests/schedule/test_schedule_exception_api.py Developed tests for the Schedule Exception API, focusing on creation and updates of exceptions.
config/api_router.py Registered new routes for ScheduleViewSet and ScheduleExceptionViewSet in the API router.

Poem

In the realm of schedules, we now can play,
With serializers and viewsets paving the way.
CRUD operations dance, exceptions take flight,
Our API shines bright, oh what a delight!
Testing the waters, we make sure it's right,
For every appointment, our future is bright! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@anroopak anroopak marked this pull request as ready for review November 8, 2024 07:42
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Outside diff range and nitpick comments (15)
care/facility/svc/schedule.py (1)

17-23: The function works, but would it hurt to add some documentation?

While the implementation is correct, adding docstring with return type and parameter descriptions would make it slightly more maintainable.

Consider adding database indexes on valid_from and valid_to fields if they don't exist already, as this query will be frequently used. Here's a suggested improvement:

 def get_current_schedule(resource) -> QuerySet[Availability]:
+    """Get current availability schedule for a resource.
+    
+    Args:
+        resource: The resource to check availability for
+        
+    Returns:
+        QuerySet[Availability]: Currently valid availability schedules
+    """
     now = timezone.now()
     return Availability.objects.filter(
         schedule__resource=resource,
         schedule__valid_from__lte=now,
         schedule__valid_to__gte=now,
     )
care/facility/tests/schedule/test_schedule_exception_api.py (4)

20-21: Consider using a relative date instead of a hardcoded one.

While the frozen date works for now, it might be worth using a relative date (like today() or now()) to prevent the tests from failing after November 2024. Not that I'm suggesting your tests will still be running unchanged by then, but you never know... 😏

-@freeze_time("2024-11-01")
+@freeze_time(datetime.now().replace(hour=0, minute=0, second=0, microsecond=0))

23-91: Add docstring to explain this rather elaborate setup.

While I can almost follow what's happening here, a docstring explaining the test scenario would be really nice. You know, for those of us who like to understand things without having to piece together the story from comments like "he is on leave" and "he compensates".

Add something like:

@classmethod
def setUpTestData(cls) -> None:
    """Sets up test data for schedule exceptions.
    
    Creates:
    - Facility hierarchy (state -> district -> local body -> facility)
    - Users (staff and doctor)
    - Schedule (Mon-Fri: 10-12 appointments, 14-16 open slots)
    - Schedule exceptions:
        1. Leave period: Nov 5-7
        2. Compensation slot: Nov 9, 10-12
    """

112-117: Enhance error handling in schema validation.

The current implementation swallows the validation error only to raise it again immediately. While I'm sure there's a perfectly good reason for this, perhaps we could add some context to make debugging easier?

     def assert_response_schema(self, response: Response, schema: BaseModel):
         try:
             schema.validate(response.json())
         except ValidationError as e:
-            raise e
+            raise ValidationError(
+                f"Response validation failed for {schema.__name__}: {str(e)}"
+            ) from e

139-166: Enhance update test assertions.

While we're checking the slot size update, it might be slightly more thorough to verify:

  • The name was updated correctly
  • Other fields remained unchanged
  • The response contains all expected fields with correct values

Add these assertions:

# Verify updated fields
self.assertEqual(schedule_exception.name, "test schedule exception 2")

# Verify unchanged fields remain intact
self.assertEqual(schedule_exception.is_available, True)
self.assertEqual(schedule_exception.tokens_per_slot, 10)
self.assertEqual(schedule_exception.slot_type, SlotType.APPOINTMENT)
care/facility/models/schedule.py (3)

22-24: Consider future-proofing RESOURCE_TO_MODEL.

While mapping ScheduleResourceType.DOCTOR to User works for now, you might want to handle potential future resource types to avoid hardcoding.


43-63: Permissions methods in Availability ensure proper access control.

Delegating read and write permissions to the associated schedule maintains consistency. However, it might be beneficial to document this behavior.


75-76: Clarify the purpose of is_available field.

The is_available boolean field in ScheduleException may need additional context or documentation to explain how it affects scheduling logic.

care/facility/api/viewsets/schedule.py (2)

61-67: Reconsider the Order of Filter Backends for Consistency

In the ScheduleViewSet, the filter_backends list places ScheduleDRYFilterSet before filters.DjangoFilterBackend. While this may functionally work, it's generally advisable to place permission-related filters after standard filters to maintain consistency and potentially improve performance.

Consider reordering the filter_backends:

 filter_backends = [
-    ScheduleDRYFilterSet,
     filters.DjangoFilterBackend,
+    ScheduleDRYFilterSet,
 ]

This minor adjustment aligns the ordering with common practices, applying standard filters before permission checks.


104-107: Explicitly Handle destroy Action in Serializer Selection

In ScheduleExceptionViewSet, the get_serializer_class method doesn't explicitly handle the destroy action. While it may default to the read-only serializer, specifying it enhances code clarity.

Add explicit handling for the destroy action:

 def get_serializer_class(self):
     if self.action in ("create", "update", "partial_update"):
         return ScheduleExceptionCreateSerializer
+    if self.action == "destroy":
+        return ScheduleExceptionReadOnlySerializer
     return ScheduleExceptionReadOnlySerializer

This explicit declaration makes the code more readable and maintainable.

care/facility/tests/schedule/test_schedule_api.py (2)

132-135: Redundant try-except block around schema validation

Catching a ValidationError only to re-raise it doesn't add much value. It might be cleaner to let the exception propagate naturally.

Simplify the method like this:

-        try:
-            schema.parse_obj(response.json())
-        except ValidationError as e:
-            raise e
+        schema.parse_obj(response.json())

92-98: Refactor URL construction for better maintainability

Building URLs by string concatenation might work, but using Django's reverse function or URL resolvers can make your code more reliable and easier to maintain, especially if the URL patterns change.

Consider refactoring as follows:

+from django.urls import reverse
...
     def get_url(self, entry_id=None, action=None):
-        base_url = f"/api/v1/facility/{self.facility.external_id}/schedule/"
+        base_url = reverse('facility-schedule-list', kwargs={'facility_external_id': self.facility.external_id})
         if entry_id is not None:
-            base_url += f"{entry_id}/"
+            base_url = reverse('facility-schedule-detail', kwargs={'facility_external_id': self.facility.external_id, 'pk': entry_id})
         if action is not None:
             base_url += f"{action}/"
         return base_url
care/facility/api/serializers/schedule.py (3)

71-77: Consider consolidating the validate_days_of_week method

The validate_days_of_week method in AvailabilityCreateSerializer duplicates logic that also exists in AvailabilityUpdateSerializer. Refactoring this validation into a shared method or a mixin would reduce code redundancy and improve maintainability.


94-104: Refactor duplicated validate_doctor_username logic

The validate_doctor_username method in ScheduleCreateSerializer is identical to the one in ScheduleExceptionCreateSerializer. It might be beneficial to extract this validation into a shared utility function or base class to avoid repetition.


105-130: Streamline resource creation logic

The create method contains repetitive logic for handling doctor_username and creating a SchedulableResource, which is also present in other serializers. Consolidating this logic into a shared method could enhance code clarity and reduce duplication.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 39c7aa4 and 7f0dfd0.

📒 Files selected for processing (7)
  • care/facility/api/serializers/schedule.py (1 hunks)
  • care/facility/api/viewsets/schedule.py (1 hunks)
  • care/facility/models/schedule.py (1 hunks)
  • care/facility/svc/schedule.py (1 hunks)
  • care/facility/tests/schedule/test_schedule_api.py (1 hunks)
  • care/facility/tests/schedule/test_schedule_exception_api.py (1 hunks)
  • config/api_router.py (2 hunks)
🔇 Additional comments (12)
care/facility/svc/schedule.py (1)

1-10: LGTM! The imports are perfectly organized.

The imports are well-structured and necessary for the functionality. How refreshing to see proper import organization!

config/api_router.py (2)

86-89: How refreshing to see properly formatted imports!

The new imports follow the existing patterns and maintain alphabetical ordering. It's almost as if someone actually read the coding guidelines.


225-232: New routes look perfectly fine, but let's verify anyway.

The route registrations follow the established patterns and are properly nested under the facility router. The basenames are consistent with the naming convention.

Let's make absolutely sure these new routes don't conflict with any existing ones:

✅ Verification successful

The previous results weren't quite as comprehensive as one might hope. Let's get a better view of the routing context and any potential conflicts.


The previous results suggest these are new viewsets, but let's just take one final look at the implementation and routing context. Not that we don't trust the initial setup, but you know how these things can be... surprisingly interesting.


Well, someone did their homework with these routes.

After a thorough inspection (perhaps more thorough than strictly necessary), these new schedule routes are perfectly fine. They're properly nested under the facility router, have unique basenames, and the viewset implementations are quite... meticulously done, I must say. No conflicts with existing routes, and the permission classes are properly configured – not that we were worried.

  • The ScheduleViewSet and ScheduleExceptionViewSet are properly implemented in their own file
  • The router registrations follow the established facility nested router pattern
  • No conflicting routes found in the entire codebase
  • Proper permission classes and filtering are in place (they really thought of everything)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential route conflicts
# Expected: No matches for similar route patterns

echo "Checking for potential route conflicts..."
rg -i "schedule.*basename.*facility" --type py

echo "Checking for similar route patterns..."
rg -i "register\(['\"]schedule" --type py

echo "Checking URL patterns..."
rg -i "path.*schedule" --type py

Length of output: 451


Script:

#!/bin/bash
# Let's do a more thorough check with context lines

echo "Checking router registrations with context..."
rg -B 5 -A 5 "register\(['\"]schedule" --type py

echo "Checking ViewSet imports and definitions..."
rg -l "class ScheduleViewSet|class ScheduleExceptionViewSet" --type py

echo "Checking all URL patterns in urls.py files..."
fd "urls\.py$" --exec rg -l "schedule" {}

Length of output: 507


Script:

#!/bin/bash
# One last check to be absolutely certain

echo "Checking the schedule viewset implementation..."
cat care/facility/api/viewsets/schedule.py

echo "Checking the entire router file for context..."
cat config/api_router.py

Length of output: 16878

care/facility/models/schedule.py (5)

13-16: Addition of SlotType choices is clear and well-defined.

The SlotType class correctly defines the slot types with appropriate integer choices, enhancing readability and maintainability.


18-20: ScheduleResourceType is appropriately set up for resource categorization.

Defining ScheduleResourceType with integer choices lays a solid foundation for future extensions of resource types.


27-34: SchedulableResource class is well-structured with generic relations.

Using GenericForeignKey allows flexibility in associating various resource types, which is a smart choice here.


36-41: Schedule model effectively links resources with scheduling details.

The fields are appropriately defined to capture the necessary scheduling information.


54-54: ⚠️ Potential issue

Double-check the use of mutable default arguments.

Using default=list in days_of_week could lead to unexpected behavior due to the mutable default. It would be prudent to use default_factory=list instead.

Apply this diff to fix the issue:

-    days_of_week = models.JSONField(default=list)
+    from django.db.models import JSONField
+    from django.contrib.postgres.fields import ArrayField
+
+    days_of_week = ArrayField(models.CharField(max_length=3), default=list)

Alternatively, if using Django 3.1+, you can use default=list, but ensure that it's handling as expected.

Likely invalid or redundant comment.

care/facility/api/viewsets/schedule.py (1)

70-75: Confirm Serializer Selection for Custom Actions

In ScheduleViewSet, the get_serializer_class method includes "delete_availability" in its action check. Since delete_availability is a custom action, ensure that it correctly triggers the intended serializer.

Run the following script to confirm that the action name matches and the serializer is appropriate:

✅ Verification successful

Everything Seems Perfectly Fine Here

The delete_availability action is properly decorated and the serializer selection is actually quite sensible. The action is used to delete an availability record, and while it technically doesn't need a serializer for deletion, using ScheduleCreateSerializer is appropriate since it would have the necessary field validations for the schedule context. Not that anyone asked, but it's all properly implemented.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify action names and serializer classes in ScheduleViewSet.

# Expected: The 'delete_availability' action should be present and correctly named.

ast-grep --pattern $'@action(\n  detail=$_,\n  methods=$_,\n  url_path=$_,\n)\ndef $FUNC($_):\n  $$$' care/facility/api/viewsets/schedule.py

# Check the output to ensure 'delete_availability' is correctly referenced in get_serializer_class.

Length of output: 135


Script:

#!/bin/bash
# Let's try a different approach to find the delete_availability action
rg -A 5 "delete_availability" care/facility/api/viewsets/schedule.py

# Also check the serializer imports and class definition
rg "class Schedule.*Serializer" care/facility/api/viewsets/schedule.py

# And let's see the entire viewset class structure
rg -A 2 "class ScheduleViewSet" care/facility/api/viewsets/schedule.py

Length of output: 913


Script:

#!/bin/bash
# Let's see if delete_availability is properly decorated as an action
rg -B 2 "def delete_availability" care/facility/api/viewsets/schedule.py

# And check what the ScheduleCreateSerializer actually does
rg -A 5 "class ScheduleCreateSerializer" care/facility/api/viewsets/schedule.py

# Just to be thorough, let's see if there are any imports of these serializers
rg "from .*(Schedule.*Serializer)" care/facility/api/viewsets/schedule.py

Length of output: 391


Script:

#!/bin/bash
# One last attempt to find the action decorator details
rg -B 3 'url_path="availability' care/facility/api/viewsets/schedule.py

# And let's check the entire file content since we're missing some context
cat care/facility/api/viewsets/schedule.py

Length of output: 4056

care/facility/api/serializers/schedule.py (3)

151-157: Duplicate validation method detected

The validate_days_of_week method here is the same as previously mentioned in AvailabilityCreateSerializer. Refactoring it into a common location would promote reusability.


230-240: Repeated validate_doctor_username method

As noted earlier in ScheduleCreateSerializer, this validation logic is duplicated. Consider unifying this code to maintain consistency and simplify future updates.


28-35: Ensure super().to_representation returns expected data

The to_representation method in ScheduleResourceSerializer falls back to super().to_representation(instance) when the resource is not a User. Since ScheduleResourceSerializer is a Serializer, ensure that this fallback provides the intended serialization, or override it accordingly.

To confirm, run:

✅ Verification successful

The fallback to super().to_representation is perfectly fine

The to_representation method in ScheduleResourceSerializer falls back to super().to_representation(instance) for non-User resources, which is actually the correct behavior. Here's why:

  • The code shows that SchedulableResource uses a generic foreign key with resource_type and resource_id fields
  • The serializer specifically handles the User case, which is the only current use case as seen in RESOURCE_TO_MODEL = { ScheduleResourceType.DOCTOR: User }
  • For any future resource types that might be added, they would need their own specific handling in the serializer, but for now the fallback is appropriate
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the output of super().to_representation(instance) for non-User resources.

# Identify all SchedulableResource instances not linked to User models.
python -c "
from django.contrib.contenttypes.models import ContentType;
from care.facility.models.schedule import SchedulableResource;
non_user_ct = ContentType.objects.exclude(model='user');
resources = SchedulableResource.objects.filter(resource_type__in=non_user_ct);
print([r for r in resources]);
"

Length of output: 385


Script:

#!/bin/bash
# Let's examine the inheritance hierarchy and implementation details
# First, find the base class of ScheduleResourceSerializer
rg -A 5 "class ScheduleResourceSerializer" 

# Then look for any other serializers that might handle non-User resources
rg "class.*Serializer.*Schedule" -A 5

# Also check for any resource-related models to understand the data structure
rg "class.*Resource.*\(models\.Model\)" -A 5

Length of output: 709


Script:

#!/bin/bash
# Let's check the model definition to understand what fields are available
rg -B 5 -A 10 "class SchedulableResource"

# And look for the base serializer implementation
fd -e py -x cat {} \; -p "serializers"

# Check for any related resource types or models
rg "resource_type = " -B 2 -A 2

Length of output: 235672

Comment on lines +12 to +14
def is_available(resource, from_datetime: datetime, to_datetime: datetime) -> bool:
msg = "Not implemented"
raise NotImplementedError(msg)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Oh, look who decided not to implement the availability check!

The is_available function is a critical component for checking resource availability, but it's currently just a placeholder. This could block dependent features from working correctly.

Would you like me to help implement this function? Here's what we could consider:

  1. Check for overlapping schedules in the given time range
  2. Verify against any exceptions
  3. Handle edge cases like timezone considerations

Let me know if you'd like me to open an issue to track this implementation.

Comment on lines +26 to +27
def get_exceptions(resource) -> QuerySet[AvailabilityException]:
return AvailabilityException.objects.filter(resource=resource)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Interesting choice to return all exceptions without any time filtering.

The function returns all exceptions for a resource, including potentially expired ones. Might want to consider adding date filtering unless there's a very good reason not to.

Consider this improvement:

 def get_exceptions(resource) -> QuerySet[AvailabilityException]:
+    """Get active availability exceptions for a resource.
+    
+    Args:
+        resource: The resource to get exceptions for
+        
+    Returns:
+        QuerySet[AvailabilityException]: Active availability exceptions
+    """
+    now = timezone.now()
     return AvailabilityException.objects.filter(
-        resource=resource
+        resource=resource,
+        start_time__lte=now,
+        end_time__gte=now
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_exceptions(resource) -> QuerySet[AvailabilityException]:
return AvailabilityException.objects.filter(resource=resource)
def get_exceptions(resource) -> QuerySet[AvailabilityException]:
"""Get active availability exceptions for a resource.
Args:
resource: The resource to get exceptions for
Returns:
QuerySet[AvailabilityException]: Active availability exceptions
"""
now = timezone.now()
return AvailabilityException.objects.filter(
resource=resource,
start_time__lte=now,
end_time__gte=now
)

Comment on lines 118 to 166
def test_create_schedule(self):
# test create schedule for doctor user in december month
data = {
"name": "test schedule",
"doctor_username": self.doctor_user.username,
"is_available": True,
"datetime_range": [
"2024-12-09T10:00:00",
"2024-12-09T12:00:00",
],
"slot_type": SlotType.APPOINTMENT,
"slot_size_in_minutes": 30,
"tokens_per_slot": 10,
}
response = self.client.post(self.get_url(), data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

# check if the response is as per the spec
response_json_schema = self.get_response_schema()
self.assert_response_schema(response, response_json_schema)

def test_update_schedule_exception(self):
schedule_exception = ScheduleException.objects.create(
resource=self.schedulable_resource,
name="test schedule exception",
is_available=True,
datetime_range=(
datetime(2024, 12, 9, hour=10),
datetime(2024, 12, 9, hour=12),
),
slot_size_in_minutes=30,
tokens_per_slot=10,
slot_type=SlotType.APPOINTMENT,
)
data = {
"name": "test schedule exception 2",
"slot_size_in_minutes": 45,
}
url = self.get_url(schedule_exception.external_id)
response = self.client.patch(url, data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)

# check if the response is as per the spec
response_json_schema = self.get_response_schema()
self.assert_response_schema(response, response_json_schema)

# check if the availability is updated
schedule_exception.refresh_from_db()
self.assertEqual(schedule_exception.slot_size_in_minutes, 45)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Add test cases for validation and authentication scenarios

I see we have IsAuthenticated and DRYPermissions in place, yet somehow we're not testing them. How... interesting. 😊 Here are the specific test cases you should add:

  • Authentication scenarios:

    • Unauthenticated access attempt
    • Access with non-facility staff user
    • Access with incorrect facility permissions
  • Validation scenarios:

    • Invalid datetime_range (end before start)
    • Missing required fields (the serializer seems quite... trusting)
    • Invalid slot_size_in_minutes (negative or zero for appointment slots)
    • Overlapping schedule exceptions (unless that's intentionally allowed?)

The serializer is surprisingly permissive with minimal validation, which might be fine if that's what you want, but it would be lovely to have tests confirming that's intentional.

🔗 Analysis chain

Add test cases for error scenarios and authentication.

I see we're testing the happy path, which is... nice. But what about:

  • Invalid date ranges
  • Overlapping schedule exceptions
  • Unauthorized access attempts
  • Invalid slot sizes
  • Missing required fields

You know, all those pesky edge cases that users somehow always manage to find in production? 😊

Would you like me to help generate test cases for these scenarios?

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any other test files for schedule exceptions
# that might contain these test cases

echo "Searching for other schedule exception test files..."
fd "test.*schedule.*exception.*\.py$"

echo "Checking for existing error scenario tests..."
rg -i "test.*(invalid|error|fail|unauthorized|authentication)" \
   -g "test_*schedule*.py"

Length of output: 404


Script:

#!/bin/bash
# Let's look at the actual test file content to see what we might be missing
echo "Checking the content of the test file..."
cat care/facility/tests/schedule/test_schedule_exception_api.py

# Let's also check for any validation logic in the related models/views
echo -e "\nChecking for validation logic in schedule exception related files..."
rg -A 5 "class ScheduleException" 

# Let's see if there are any existing validation methods
echo -e "\nChecking for validation methods..."
rg "def (validate|clean)" -g "*schedule*exception*.py" -A 3

Length of output: 9435


Script:

#!/bin/bash
# Let's check the serializer implementation for any validation logic
echo "Checking serializer validation..."
rg -A 10 "class ScheduleExceptionCreateSerializer|class ScheduleExceptionUpdateSerializer" care/facility/api/serializers/schedule.py

# Let's also check if there are any existing test files for the serializer
echo -e "\nChecking for serializer test files..."
fd "test.*schedule.*serializer.*\.py$"

# And let's look for any authentication setup in the viewset
echo -e "\nChecking viewset authentication..."
rg -A 10 "class ScheduleExceptionViewSet" care/facility/api/viewsets/schedule.py

Length of output: 1626

Comment on lines 65 to 76
class ScheduleException(FacilityBaseModel, FacilityRelatedPermissionMixin):
resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
name = models.CharField(max_length=255, null=False, blank=False)

slot_type = models.IntegerField(
choices=SlotType.choices, default=SlotType.OPEN, null=False, blank=False
)
slot_size_in_minutes = models.IntegerField(null=False, blank=False, default=0)
tokens_per_slot = models.IntegerField(null=False, blank=False, default=0)

is_available = models.BooleanField(null=False, blank=False)
datetime_range = DateTimeRangeField(null=False, blank=False)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing permission methods in ScheduleException.

Unlike Availability, the ScheduleException model lacks has_object_read_permission and has_object_write_permission methods. This inconsistency might lead to unauthorized access or errors.

Consider adding the following methods to ensure consistent permission handling:

+    def has_object_read_permission(self, request):
+        return self.resource.has_object_read_permission(request)
+
+    def has_object_write_permission(self, request):
+        return self.resource.has_object_write_permission(request)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class ScheduleException(FacilityBaseModel, FacilityRelatedPermissionMixin):
resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
name = models.CharField(max_length=255, null=False, blank=False)
slot_type = models.IntegerField(
choices=SlotType.choices, default=SlotType.OPEN, null=False, blank=False
)
slot_size_in_minutes = models.IntegerField(null=False, blank=False, default=0)
tokens_per_slot = models.IntegerField(null=False, blank=False, default=0)
is_available = models.BooleanField(null=False, blank=False)
datetime_range = DateTimeRangeField(null=False, blank=False)
class ScheduleException(FacilityBaseModel, FacilityRelatedPermissionMixin):
resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
name = models.CharField(max_length=255, null=False, blank=False)
slot_type = models.IntegerField(
choices=SlotType.choices, default=SlotType.OPEN, null=False, blank=False
)
slot_size_in_minutes = models.IntegerField(null=False, blank=False, default=0)
tokens_per_slot = models.IntegerField(null=False, blank=False, default=0)
is_available = models.BooleanField(null=False, blank=False)
datetime_range = DateTimeRangeField(null=False, blank=False)
def has_object_read_permission(self, request):
return self.resource.has_object_read_permission(request)
def has_object_write_permission(self, request):
return self.resource.has_object_write_permission(request)

Comment on lines +82 to +91
def delete_availability(self, *args, availability_external_id=None, **kwargs):
try:
availability = Availability.objects.get(
external_id=availability_external_id,
schedule__external_id=kwargs["external_id"],
)
availability.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Availability.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve Exception Handling in delete_availability Method

The current exception handling in the delete_availability method only catches Availability.DoesNotExist, which might not differentiate between an availability that doesn't exist and one that doesn't belong to the specified schedule.

Enhance the exception handling to provide more informative responses:

 def delete_availability(self, *args, availability_external_id=None, **kwargs):
     try:
         availability = Availability.objects.get(
             external_id=availability_external_id,
             schedule__external_id=kwargs["external_id"],
         )
         availability.delete()
         return Response(status=status.HTTP_204_NO_CONTENT)
-    except Availability.DoesNotExist:
+    except Availability.DoesNotExist as e:
+        if Availability.objects.filter(external_id=availability_external_id).exists():
+            return Response(
+                {"detail": "Availability does not belong to this schedule."},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
         return Response(status=status.HTTP_404_NOT_FOUND)

This change provides clearer feedback to the client, distinguishing between a missing availability and an unauthorized deletion attempt.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def delete_availability(self, *args, availability_external_id=None, **kwargs):
try:
availability = Availability.objects.get(
external_id=availability_external_id,
schedule__external_id=kwargs["external_id"],
)
availability.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Availability.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
def delete_availability(self, *args, availability_external_id=None, **kwargs):
try:
availability = Availability.objects.get(
external_id=availability_external_id,
schedule__external_id=kwargs["external_id"],
)
availability.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Availability.DoesNotExist as e:
if Availability.objects.filter(external_id=availability_external_id).exists():
return Response(
{"detail": "Availability does not belong to this schedule."},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(status=status.HTTP_404_NOT_FOUND)

Comment on lines 266 to 280
class ScheduleExceptionUpdateSerializer(serializers.ModelSerializer):
datetime_range = serializers.ListField(child=serializers.DateTimeField())

class Meta:
model = ScheduleException
fields = (
"name",
"is_available",
"datetime_range",
"slot_type",
"slot_size_in_minutes",
"tokens_per_slot",
)

def to_representation(self, instance: ScheduleException) -> dict[str, any]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure datetime_range is properly converted during updates

Similar to the create serializer, the update process must convert the datetime_range list to a Range object to prevent issues when saving to the database.

Implement the conversion in the update method:

def update(self, instance, validated_data):
    datetime_range_list = validated_data.pop('datetime_range', None)
    if datetime_range_list:
        validated_data['datetime_range'] = Range(*datetime_range_list)
    return super().update(instance, validated_data)

Comment on lines +80 to +93
class ScheduleCreateSerializer(serializers.ModelSerializer):
doctor_username = serializers.CharField(required=False)
availability = AvailabilityCreateSerializer(many=True)

class Meta:
model = Schedule
fields = (
"doctor_username",
"name",
"valid_from",
"valid_to",
"availability",
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate that valid_from precedes valid_to

Currently, there is no check to ensure that valid_from is earlier than valid_to. This oversight might allow schedules with invalid date ranges.

Add a validate method to enforce this constraint:

def validate(self, data):
    if data['valid_from'] >= data['valid_to']:
        raise serializers.ValidationError("`valid_from` must be earlier than `valid_to`.")
    return data

Comment on lines +160 to +171
class ScheduleUpdateSerializer(serializers.ModelSerializer):
availability = AvailabilityUpdateSerializer(many=True)

class Meta:
model = Schedule
fields = (
"name",
"valid_from",
"valid_to",
"availability",
)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for date range consistency

As with the create serializer, ensure that in ScheduleUpdateSerializer, valid_from is earlier than valid_to to maintain valid schedule durations.

Include the validate method:

def validate(self, data):
    if data['valid_from'] >= data['valid_to']:
        raise serializers.ValidationError("`valid_from` must be earlier than `valid_to`.")
    return data

Comment on lines +172 to +185
def update(self, instance, validated_data):
availability_data = validated_data.pop("availability", [])
schedule = super().update(instance, validated_data)

for availability in availability_data:
if external_id := availability.pop("external_id", None):
Availability.objects.filter(
external_id=external_id, schedule=schedule
).update(**availability)
else:
availability["schedule"] = schedule
Availability.objects.create(**availability)

return schedule
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Wrap update operations in a transaction

The update method performs multiple database operations which could lead to partial updates if an error occurs. Wrapping these operations in a transaction ensures atomicity.

Implement transaction management:

from django.db import transaction

def update(self, instance, validated_data):
    availability_data = validated_data.pop("availability", [])
    with transaction.atomic():
        schedule = super().update(instance, validated_data)
        for availability in availability_data:
            # Existing logic
    return schedule

Comment on lines +105 to +130
def create(self, validated_data: dict) -> Schedule:
if doctor_username := validated_data.pop("doctor_username", None):
facility = Facility.objects.get(
external_id=self.context["request"].parser_context["kwargs"][
"facility_external_id"
]
)
doctor = FacilityUser.objects.get(
user__username=doctor_username, facility=facility
).user

user_content_type = ContentType.objects.get_for_model(doctor)
resource, _ = SchedulableResource.objects.get_or_create(
facility=facility,
resource_id=doctor.id,
resource_type=user_content_type,
)
validated_data["resource"] = resource

availability_data = validated_data.pop("availability")
schedule = super().create(validated_data)
for availability in availability_data:
availability["schedule"] = schedule
Availability.objects.create(**availability)
return schedule

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider transaction management in creation

Similarly, the create method could benefit from being wrapped in a transaction to ensure all related objects are created atomically.

Update the create method:

from django.db import transaction

def create(self, validated_data):
    with transaction.atomic():
        # Existing creation logic
    return schedule

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (12)
care/facility/models/schedule.py (4)

26-28: Perhaps consider a more... extensive resource mapping?

The RESOURCE_TO_MODEL dictionary currently only maps DOCTOR to User. While this might work for now, it seems a bit... limiting. Future resource types would require code changes.

Consider:

  1. Adding a comment explaining why only DOCTOR is mapped
  2. Or adding other potential resource types (nurses, equipment, rooms) if they're planned
RESOURCE_TO_MODEL = {
    ScheduleResourceType.DOCTOR: User,
    # Future resource types:
    # ScheduleResourceType.NURSE: Staff,
    # ScheduleResourceType.ROOM: Room,
}

31-37: Your queries might appreciate some indexes

The SchedulableResource model uses GenericForeignKey which typically benefits from database indexes, but I couldn't help but notice they're missing.

Consider adding indexes to improve query performance:

 class SchedulableResource(FacilityBaseModel):
     facility = models.ForeignKey(
         "Facility", on_delete=models.CASCADE, null=False, blank=False
     )
-    resource_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
-    resource_id = models.PositiveIntegerField()
+    resource_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, db_index=True)
+    resource_id = models.PositiveIntegerField(db_index=True)
     resource = GenericForeignKey("resource_type", "resource_id")

40-47: A tiny performance enhancement opportunity

The filter_by_resource method gets ContentType on every call. Not that I'm counting, but that's one database query per call that could be avoided.

Consider caching the ContentType lookup:

 class ScheduleBaseManager(models.Manager):
+    def get_content_type(self, model_class):
+        if not hasattr(self, '_content_type_cache'):
+            self._content_type_cache = {}
+        if model_class not in self._content_type_cache:
+            self._content_type_cache[model_class] = ContentType.objects.get_for_model(model_class)
+        return self._content_type_cache[model_class]
+
     def filter_by_resource(self, resource):
         return self.filter(
             resource__resource_id=resource.id,
-            resource__resource_type=ContentType.objects.get_for_model(
-                resource.__class__
-            ),
+            resource__resource_type=self.get_content_type(resource.__class__),
         )

81-92: Time ranges could use some boundaries

The datetime_range field lacks validation for reasonable time ranges. We probably don't want exceptions scheduled for the year 2525.

Consider adding validation for reasonable date ranges:

 class ScheduleException(FacilityBaseModel, FacilityRelatedPermissionMixin):
+    MAX_EXCEPTION_DAYS = 30  # or whatever makes sense for your use case
+
     # ... existing fields ...
+
+    def clean(self):
+        super().clean()
+        if self.datetime_range:
+            duration = self.datetime_range.upper - self.datetime_range.lower
+            if duration.days > self.MAX_EXCEPTION_DAYS:
+                raise ValidationError({
+                    'datetime_range': f'Exception duration cannot exceed {self.MAX_EXCEPTION_DAYS} days'
+                })
care/facility/models/patient_base.py (1)

20-21: Remove the extra blank line... if it's not too much trouble.

There are two consecutive blank lines where one would suffice.

 def reverse_choices_with_label(choices):
     output = {}
     for choice in choices:
         output[choice[1].lower()] = choice[0]
     return output
-

 SYMPTOM_CHOICES = [
care/facility/migrations/0466_schedulableresource_schedule_availability_and_more.py (7)

13-15: Consistent Use of Double Quotes in Dependencies

I noticed that single quotes are used in the dependencies section. To align with the project's coding standards, please replace single quotes with double quotes.

Apply this diff to correct the quotes:

13-            ('contenttypes', '0002_remove_content_type_name'),
14-            ('facility', '0465_merge_20240923_1045'),
+            ("contenttypes", "0002_remove_content_type_name"),
+            ("facility", "0465_merge_20240923_1045"),
🧰 Tools
🪛 Ruff

13-13: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


13-13: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


19-33: String Quoting Style in SchedulableResource Model Definition

Single quotes are used in the model definition. For consistency with the preferred style, please use double quotes.

Here's the suggested change:

19-            migrations.CreateModel(
20-                name='SchedulableResource',
+            migrations.CreateModel(
+                name="SchedulableResource",
...
21-                    ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                    ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
...
28-                    ('resource_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
+                    ("resource_type", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype")),
🧰 Tools
🪛 Ruff

19-19: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


21-21: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


21-21: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


22-22: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


23-23: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


24-24: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


25-25: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


26-26: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


27-27: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


27-27: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


28-28: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


28-28: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


31-31: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-51: Consistent Quoting in Schedule Model Definition

Similarly, the Schedule model uses single quotes. Replacing them with double quotes will maintain code consistency.

Apply this diff:

35-            migrations.CreateModel(
36-                name='Schedule',
+            migrations.CreateModel(
+                name="Schedule",
...
37-                    ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                    ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
...
45-                    ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='facility.schedulableresource')),
+                    ("resource", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="facility.schedulableresource")),
🧰 Tools
🪛 Ruff

35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


37-37: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


37-37: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


38-38: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


39-39: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


40-40: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


41-41: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


42-42: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


43-43: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


44-44: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


45-45: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


45-45: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


48-48: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


53-73: Quoting Style in Availability Model Definition

The Availability model also contains single quotes. Please update them to double quotes for consistency.

Suggested changes:

53-            migrations.CreateModel(
54-                name='Availability',
+            migrations.CreateModel(
+                name="Availability",
...
61-                    ('slot_type', models.IntegerField(choices=[(1, 'Open'), (2, 'Appointment')], default=1)),
+                    ("slot_type", models.IntegerField(choices=[(1, "Open"), (2, "Appointment")], default=1)),
...
67-                    ('schedule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='facility.schedule')),
+                    ("schedule", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="facility.schedule")),
🧰 Tools
🪛 Ruff

53-53: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


56-56: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


57-57: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


58-58: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


60-60: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


61-61: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


61-61: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


61-61: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


62-62: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


63-63: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


64-64: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


65-65: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


66-66: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


67-67: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


67-67: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


70-70: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


75-94: Uniform Use of Double Quotes in ScheduleException Model

To keep the codebase consistent, please replace single quotes with double quotes in the ScheduleException model.

Here's the diff:

75-            migrations.CreateModel(
76-                name='ScheduleException',
+            migrations.CreateModel(
+                name="ScheduleException",
...
83-                    ('slot_type', models.IntegerField(choices=[(1, 'Open'), (2, 'Appointment')], default=1)),
+                    ("slot_type", models.IntegerField(choices=[(1, "Open"), (2, "Appointment")], default=1)),
...
88-                    ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='facility.schedulableresource')),
+                    ("resource", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="facility.schedulableresource")),
🧰 Tools
🪛 Ruff

75-75: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


77-77: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


77-77: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


78-78: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


79-79: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


80-80: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


81-81: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


82-82: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


83-83: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


83-83: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


83-83: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


84-84: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


85-85: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


86-86: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


87-87: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


88-88: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


88-88: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


91-91: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


43-44: Consider Allowing Null Values for valid_to Field in Schedule Model

Currently, the valid_to field is mandatory. If there are schedules that don't have an end date, allowing null values might be beneficial.

You might want to update the field definition:

44-                    ('valid_to', models.DateTimeField()),
+                    ("valid_to", models.DateTimeField(null=True, blank=True)),

This change permits schedules to have an indefinite validity period.

🧰 Tools
🪛 Ruff

43-43: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


44-44: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


86-86: Add Default Value to is_available Field in ScheduleException Model

The is_available field doesn't have a default value. To prevent potential issues during record creation, consider adding a default value.

Update the field as follows:

86-                    ('is_available', models.BooleanField()),
+                    ("is_available", models.BooleanField(default=True)),
🧰 Tools
🪛 Ruff

86-86: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f3a334d and be88b26.

📒 Files selected for processing (7)
  • care/facility/api/serializers/schedule.py (1 hunks)
  • care/facility/api/viewsets/schedule.py (1 hunks)
  • care/facility/migrations/0466_schedulableresource_schedule_availability_and_more.py (1 hunks)
  • care/facility/migrations/0468_merge_20241119_0746.py (1 hunks)
  • care/facility/models/patient_base.py (1 hunks)
  • care/facility/models/schedule.py (1 hunks)
  • care/facility/tests/schedule/test_schedule_api.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • care/facility/api/serializers/schedule.py
  • care/facility/api/viewsets/schedule.py
  • care/facility/tests/schedule/test_schedule_api.py
🧰 Additional context used
🪛 Ruff
care/facility/migrations/0466_schedulableresource_schedule_availability_and_more.py

13-13: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


13-13: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


19-19: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


21-21: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


21-21: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


22-22: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


23-23: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


24-24: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


25-25: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


26-26: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


27-27: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


27-27: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


28-28: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


28-28: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


31-31: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


37-37: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


37-37: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


38-38: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


39-39: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


40-40: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


41-41: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


42-42: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


43-43: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


44-44: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


45-45: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


45-45: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


48-48: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


53-53: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


56-56: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


57-57: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


58-58: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


60-60: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


61-61: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


61-61: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


61-61: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


62-62: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


63-63: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


64-64: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


65-65: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


66-66: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


67-67: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


67-67: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


70-70: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


75-75: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


77-77: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


77-77: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


78-78: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


79-79: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


80-80: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


81-81: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


82-82: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


83-83: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


83-83: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


83-83: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


84-84: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


85-85: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


86-86: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


87-87: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


88-88: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


88-88: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


91-91: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

care/facility/migrations/0468_merge_20241119_0746.py

9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


10-10: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


10-10: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

🔇 Additional comments (2)
care/facility/models/patient_base.py (1)

15-20: Verify the usage of this new function across the codebase.

Since this function provides an alternative way to reverse choices, we should check where and how it's being used.

✅ Verification successful

Oh, look who's using a new choices reversal function... just schedule.py

The function is currently only used in schedule.py to create REVERSE_SLOT_TYPE, while the original reverse_choices is still being used across multiple files. The new function differs by using the choice label (lowercased) as the key instead of the choice value, which might be what you wanted... or not.

  • The function is imported and used exactly once in schedule.py
  • It serves a different purpose than the original reverse_choices function
  • Other files continue to use the original reverse_choices function, as they should
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of the new function
echo "Searching for direct usage of reverse_choices_with_label:"
rg "reverse_choices_with_label" -l

echo -e "\nSearching for potential places where it could/should be used:"
rg "reverse_choices\(" -l

Length of output: 737


Script:

#!/bin/bash
# Let's see how reverse_choices_with_label is actually used in both files
echo "=== Usage in schedule.py ==="
rg "reverse_choices_with_label" care/facility/models/schedule.py -A 3 -B 3

echo -e "\n=== Usage in patient_base.py ==="
rg "reverse_choices_with_label" care/facility/models/patient_base.py -A 3 -B 3

# Let's also check the original reverse_choices implementation
echo -e "\n=== Original reverse_choices implementation ==="
rg "def reverse_choices" care/facility/models/patient_base.py -A 3 -B 3

Length of output: 1323

care/facility/migrations/0466_schedulableresource_schedule_availability_and_more.py (1)

64-64: ⚠️ Potential issue

Validate the Default Value for days_of_week Field

Using default=list for the days_of_week field could lead to all instances sharing the same list. To ensure each instance has its own list, it's better to use default=list as a callable.

Modify the field definition:

64-                    ('days_of_week', models.JSONField(default=list)),
+                    ("days_of_week", models.JSONField(default=list)),

Wait, the field already uses default=list. Hmm, perhaps I misread that. Disregard this if default=list is correctly used.

🧰 Tools
🪛 Ruff

64-64: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

Comment on lines +50 to +56
class Schedule(FacilityBaseModel, FacilityRelatedPermissionMixin):
resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
name = models.CharField(max_length=255, null=False, blank=False)
valid_from = models.DateTimeField(null=False, blank=False)
valid_to = models.DateTimeField(null=False, blank=False)

objects = ScheduleBaseManager()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Time travel not supported (yet)

The Schedule model doesn't validate that valid_from is before valid_to. I assume we're not building a time machine?

Add validation to ensure temporal sanity:

 class Schedule(FacilityBaseModel, FacilityRelatedPermissionMixin):
     resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
     name = models.CharField(max_length=255, null=False, blank=False)
     valid_from = models.DateTimeField(null=False, blank=False)
     valid_to = models.DateTimeField(null=False, blank=False)

     objects = ScheduleBaseManager()
+
+    def clean(self):
+        super().clean()
+        if self.valid_from and self.valid_to and self.valid_from >= self.valid_to:
+            raise ValidationError({
+                'valid_to': 'End time must be after start time'
+            })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class Schedule(FacilityBaseModel, FacilityRelatedPermissionMixin):
resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
name = models.CharField(max_length=255, null=False, blank=False)
valid_from = models.DateTimeField(null=False, blank=False)
valid_to = models.DateTimeField(null=False, blank=False)
objects = ScheduleBaseManager()
class Schedule(FacilityBaseModel, FacilityRelatedPermissionMixin):
resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE)
name = models.CharField(max_length=255, null=False, blank=False)
valid_from = models.DateTimeField(null=False, blank=False)
valid_to = models.DateTimeField(null=False, blank=False)
objects = ScheduleBaseManager()
def clean(self):
super().clean()
if self.valid_from and self.valid_to and self.valid_from >= self.valid_to:
raise ValidationError({
'valid_to': 'End time must be after start time'
})

Comment on lines 59 to 78
class Availability(FacilityBaseModel, FacilityRelatedPermissionMixin):
schedule = models.ForeignKey(
"Schedule", on_delete=models.CASCADE, null=False, blank=False
)
name = models.CharField(max_length=255, null=False, blank=False)
slot_type = models.IntegerField(
choices=SlotType.choices, default=SlotType.OPEN, null=False, blank=False
)
slot_size_in_minutes = models.IntegerField(null=False, blank=False, default=0)
tokens_per_slot = models.IntegerField(null=False, blank=False, default=0)

days_of_week = models.JSONField(default=list)
start_time = models.TimeField(null=False, blank=False)
end_time = models.TimeField(null=False, blank=False)

def has_object_read_permission(self, request):
return self.schedule.has_object_read_permission(request)

def has_object_write_permission(self, request):
return self.schedule.has_object_write_permission(request)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Apparently any day can be a Monday

The days_of_week JSONField accepts any value without validation. Not that anyone would intentionally put invalid days, but...

Add validation for the days_of_week field:

 class Availability(FacilityBaseModel, FacilityRelatedPermissionMixin):
+    VALID_DAYS = range(0, 7)  # 0 = Monday, 6 = Sunday
+
     schedule = models.ForeignKey(
         "Schedule", on_delete=models.CASCADE, null=False, blank=False
     )
     # ... other fields ...
     days_of_week = models.JSONField(default=list)
+
+    def clean(self):
+        super().clean()
+        if not all(day in self.VALID_DAYS for day in self.days_of_week):
+            raise ValidationError({
+                'days_of_week': 'Invalid day value. Must be between 0 (Monday) and 6 (Sunday)'
+            })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class Availability(FacilityBaseModel, FacilityRelatedPermissionMixin):
schedule = models.ForeignKey(
"Schedule", on_delete=models.CASCADE, null=False, blank=False
)
name = models.CharField(max_length=255, null=False, blank=False)
slot_type = models.IntegerField(
choices=SlotType.choices, default=SlotType.OPEN, null=False, blank=False
)
slot_size_in_minutes = models.IntegerField(null=False, blank=False, default=0)
tokens_per_slot = models.IntegerField(null=False, blank=False, default=0)
days_of_week = models.JSONField(default=list)
start_time = models.TimeField(null=False, blank=False)
end_time = models.TimeField(null=False, blank=False)
def has_object_read_permission(self, request):
return self.schedule.has_object_read_permission(request)
def has_object_write_permission(self, request):
return self.schedule.has_object_write_permission(request)
class Availability(FacilityBaseModel, FacilityRelatedPermissionMixin):
VALID_DAYS = range(0, 7) # 0 = Monday, 6 = Sunday
schedule = models.ForeignKey(
"Schedule", on_delete=models.CASCADE, null=False, blank=False
)
name = models.CharField(max_length=255, null=False, blank=False)
slot_type = models.IntegerField(
choices=SlotType.choices, default=SlotType.OPEN, null=False, blank=False
)
slot_size_in_minutes = models.IntegerField(null=False, blank=False, default=0)
tokens_per_slot = models.IntegerField(null=False, blank=False, default=0)
days_of_week = models.JSONField(default=list)
start_time = models.TimeField(null=False, blank=False)
end_time = models.TimeField(null=False, blank=False)
def clean(self):
super().clean()
if not all(day in self.VALID_DAYS for day in self.days_of_week):
raise ValidationError({
'days_of_week': 'Invalid day value. Must be between 0 (Monday) and 6 (Sunday)'
})
def has_object_read_permission(self, request):
return self.schedule.has_object_read_permission(request)
def has_object_write_permission(self, request):
return self.schedule.has_object_write_permission(request)

Comment on lines 15 to 20
def reverse_choices_with_label(choices):
output = {}
for choice in choices:
output[choice[1].lower()] = choice[0]
return output

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

The new function could use some improvements... if you don't mind.

While the function works for basic cases, there are a few concerns that probably should be addressed:

  1. Potential key collisions if different labels map to the same lowercase string
  2. No handling of None/null values
  3. Inconsistent with Django's choice convention of using the first element as the key

Here's a slightly more robust implementation:

 def reverse_choices_with_label(choices):
+    """
+    Creates a mapping of lowercase choice labels to their values.
+    
+    Args:
+        choices: List of tuples (value, label)
+    
+    Returns:
+        Dict mapping lowercase labels to their corresponding values
+    
+    Raises:
+        ValueError: If multiple labels map to the same lowercase string
+    """
     output = {}
     for choice in choices:
+        if choice[1] is None:
+            continue
+        key = choice[1].lower()
+        if key in output:
+            raise ValueError(f"Multiple labels map to '{key}'")
         output[choice[1].lower()] = choice[0]
     return output
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def reverse_choices_with_label(choices):
output = {}
for choice in choices:
output[choice[1].lower()] = choice[0]
return output
def reverse_choices_with_label(choices):
"""
Creates a mapping of lowercase choice labels to their values.
Args:
choices: List of tuples (value, label)
Returns:
Dict mapping lowercase labels to their corresponding values
Raises:
ValueError: If multiple labels map to the same lowercase string
"""
output = {}
for choice in choices:
if choice[1] is None:
continue
key = choice[1].lower()
if key in output:
raise ValueError(f"Multiple labels map to '{key}'")
output[choice[1].lower()] = choice[0]
return output

options={
'abstract': False,
},
bases=(models.Model, care.facility.models.mixins.permissions.facility.FacilityRelatedPermissionMixin),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid Specifying bases in Migration Model Definitions

Including the bases argument in migration model definitions is unconventional and might lead to migration issues. Django handles model inheritance, and specifying bases in migrations can cause inconsistencies.

Consider removing the bases argument from the model definitions in the migration file.

Suggested change:

50-                bases=(models.Model, care.facility.models.mixins.permissions.facility.FacilityRelatedPermissionMixin),
+                # Removed 'bases' argument to allow Django to manage inheritance
72-                bases=(models.Model, care.facility.models.mixins.permissions.facility.FacilityRelatedPermissionMixin),
+                # Removed 'bases' argument to allow Django to manage inheritance
93-                bases=(models.Model, care.facility.models.mixins.permissions.facility.FacilityRelatedPermissionMixin),
+                # Removed 'bases' argument to allow Django to manage inheritance

Also applies to: 72-72, 93-93

@anroopak anroopak requested a review from a team as a code owner November 21, 2024 02:31
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
care/facility/tests/schedule/test_schedule_api.py (5)

20-21: A docstring would be lovely here

While the code is self-documenting (sort of), a class docstring explaining the test scenarios would make it even better. You know, for those days when we're not feeling particularly psychic about the test coverage.


170-171: Those assertions could use a bit more... personality

Adding assertion messages would make test failures more informative. Not that your code would ever fail, but you know... just in case.

-        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED, "Schedule creation failed")

176-194: Might want to verify that deletion actually happened

The status code check is nice, but wouldn't it be even nicer to verify that the availability actually doesn't exist anymore? Just a thought...

         self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+        self.assertFalse(
+            Availability.objects.filter(external_id=new_availability.external_id).exists(),
+            "Availability should not exist after deletion"
+        )

198-200: Those variable names could be a tad more... descriptive

existing_availability_external_ids is quite a mouthful. Maybe we could make it more... concise?

-        existing_availability_external_ids = list(
+        existing_ids = list(
             self.schedule.availability_set.all().values_list("external_id", flat=True)
         )

239-257: Maybe we could test a few more filter combinations?

The current test covers the happy path, but what about testing:

  • Invalid date ranges
  • Non-existent doctor username
  • Missing filter parameters
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between be88b26 and 6f5cc57.

📒 Files selected for processing (6)
  • care/facility/api/serializers/schedule.py (1 hunks)
  • care/facility/migrations/0469_remove_scheduleexception_datetime_range_and_more.py (1 hunks)
  • care/facility/models/patient_base.py (1 hunks)
  • care/facility/models/schedule.py (1 hunks)
  • care/facility/tests/schedule/test_schedule_api.py (1 hunks)
  • care/facility/tests/schedule/test_schedule_exception_api.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • care/facility/api/serializers/schedule.py
  • care/facility/models/patient_base.py
  • care/facility/models/schedule.py
  • care/facility/tests/schedule/test_schedule_exception_api.py
🧰 Additional context used
🪛 Ruff
care/facility/migrations/0469_remove_scheduleexception_datetime_range_and_more.py

9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


15-15: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


18-18: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


19-19: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


23-23: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


24-24: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


25-25: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


29-29: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


30-30: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


34-34: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


36-36: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


40-40: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


41-41: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


42-42: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


46-46: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


47-47: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


48-48: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


52-52: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


53-53: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


57-57: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


58-58: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

🔇 Additional comments (3)
care/facility/migrations/0469_remove_scheduleexception_datetime_range_and_more.py (2)

13-16: ⚠️ Potential issue

Verify data migration strategy for removed datetime_range field.

I couldn't help but notice that we're removing the datetime_range field without any apparent data migration strategy. It would be absolutely wonderful if you could clarify how existing data will be handled.

🧰 Tools
🪛 Ruff

14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


15-15: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


51-60: Consider data migration for slot_type changes.

The slot type changes look mostly fine, but I couldn't help wondering about existing records. While the default of 1 ('Open') is reasonable, existing records might need a more nuanced migration strategy.

🧰 Tools
🪛 Ruff

52-52: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


53-53: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


57-57: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


58-58: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


59-59: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

care/facility/tests/schedule/test_schedule_api.py (1)

5-5: ⚠️ Potential issue

Oh, interesting choice importing Response from 'requests'

I see you're importing Response from the requests library, but since we're in Django REST Framework land, we should probably use rest_framework.response.Response instead. Just saying... 😊

-from requests import Response
+from rest_framework.response import Response

Likely invalid or redundant comment.

Comment on lines +23 to +38
model_name='scheduleexception',
name='end_time',
field=models.TimeField(default='00:00'),
preserve_default=False,
),
migrations.AddField(
model_name='scheduleexception',
name='reason',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='scheduleexception',
name='start_time',
field=models.TimeField(default='00:00'),
preserve_default=False,
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Reconsider default time values and add data migration.

Oh, those default values of '00:00' for time fields are... interesting. While they're removed with preserve_default=False, it might be worth considering:

  1. Adding a proper data migration strategy
  2. Using more meaningful default times for existing records

Would you mind adding a data migration to handle existing records more gracefully? Just a thought. 😊

🧰 Tools
🪛 Ruff

23-23: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


24-24: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


25-25: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


29-29: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


30-30: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


34-34: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


36-36: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

Comment on lines +39 to +50
migrations.AddField(
model_name='scheduleexception',
name='valid_from',
field=models.DateField(default='2024-11-01'),
preserve_default=False,
),
migrations.AddField(
model_name='scheduleexception',
name='valid_to',
field=models.DateField(default='2024-11-01'),
preserve_default=False,
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid hardcoding future dates in migrations.

I see we're using '2024-11-01' as a default date. While I'm sure there's a perfectly good reason for this specific date, it might be worth considering:

  1. Using a dynamic date (e.g., timezone.now())
  2. Adding a data migration instead of hardcoding dates

The current approach might cause interesting surprises when this migration runs in, say, 2025. Just saying.

🧰 Tools
🪛 Ruff

40-40: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


41-41: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


42-42: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


46-46: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


47-47: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


48-48: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant