From 8da879f1e58e3264178361d08a9d65f81b4cb198 Mon Sep 17 00:00:00 2001 From: Daniel Socas Date: Wed, 1 Apr 2026 04:04:35 +0100 Subject: [PATCH 1/5] chore: improve .gitignore coverage for Python bytecode --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9a54ec5a8..f2a3b94bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -*/__pycache__/** +# Python +__pycache__/ +*.py[cod] + +# IDE .idea/ From 2c6e22288fa7a0bafa2bf1288b2eb1f241b221fb Mon Sep 17 00:00:00 2001 From: Daniel Socas Date: Wed, 1 Apr 2026 04:15:36 +0100 Subject: [PATCH 2/5] feat(rooms): add room name filter - add RoomFilterForm with optional, case-insensitive name field - update RoomsView to filter rooms by partial name match using icontains - ignore whitespace-only input to prevent unintended filtering - pass form to template context to preserve submitted value after filtering --- pms/forms.py | 10 ++++++++++ pms/views.py | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/pms/forms.py b/pms/forms.py index f1bc68d08..b99cdbb8c 100644 --- a/pms/forms.py +++ b/pms/forms.py @@ -20,6 +20,16 @@ class Meta: } +class RoomFilterForm(forms.Form): + name = forms.CharField( + required=False, + label="Nombre", + widget=forms.TextInput( + attrs={"placeholder": "Nombre de la habitación"} + ), + ) + + class CustomerForm(ModelForm): class Meta: model = Customer diff --git a/pms/views.py b/pms/views.py index f38563933..8da7b356f 100644 --- a/pms/views.py +++ b/pms/views.py @@ -238,9 +238,18 @@ def get(self, request, pk): class RoomsView(View): def get(self, request): - # renders a list of rooms - rooms = Room.objects.all().values("name", "room_type__name", "id") + form = RoomFilterForm(request.GET or None) + rooms_qs = Room.objects.all() + + if form.is_valid(): + name = (form.cleaned_data.get("name") or "").strip() + if name: + rooms_qs = rooms_qs.filter(name__icontains=name) + + rooms = rooms_qs.values("name", "room_type__name", "id") + context = { - 'rooms': rooms + "rooms": rooms, + "form": form, } return render(request, "rooms.html", context) From a370a8cf4929f78c7adc28514e06cf17ba540d0e Mon Sep 17 00:00:00 2001 From: Daniel Socas Date: Wed, 1 Apr 2026 04:23:59 +0100 Subject: [PATCH 3/5] feat(rooms): add name filter form to rooms template --- pms/templates/rooms.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pms/templates/rooms.html b/pms/templates/rooms.html index c30929f1f..b38949c8e 100644 --- a/pms/templates/rooms.html +++ b/pms/templates/rooms.html @@ -2,6 +2,17 @@ {% block content %}

Habitaciones del hotel

+ +
+
+
+ {{ form.name }} + + Limpiar +
+
+
+ {% for room in rooms%}
From 6db3afb7452d1a9eb8000e7cd2c1edec27c5e1c8 Mon Sep 17 00:00:00 2001 From: Daniel Socas Date: Wed, 1 Apr 2026 04:32:59 +0100 Subject: [PATCH 4/5] test(rooms): add tests for room name filter functionality - cover form rendering, default listing, and partial name matching - cover case-insensitive matching and non-matching searches - cover whitespace-only input handling - cover form value preservation after filtering --- pms/tests.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/pms/tests.py b/pms/tests.py index 7ce503c2d..9b486c5e4 100644 --- a/pms/tests.py +++ b/pms/tests.py @@ -1,3 +1,75 @@ -from django.test import TestCase +from django.test import TestCase, override_settings +from django.urls import reverse -# Create your tests here. +from .models import Room + +@override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage") +class RoomsViewFilterTests(TestCase): + """ + Tests for the room list filter functionality. + + Static files storage is overridden per-class to avoid requiring + collectstatic before running tests. A project-wide test settings + file would be the cleaner long-term solution. + """ + + @classmethod + def setUpTestData(cls): + cls.rooms_url = reverse("rooms") + + cls.matching_room_one = Room.objects.create(name="Room 1.1") + cls.matching_room_two = Room.objects.create(name="Room 1.2") + cls.non_matching_room = Room.objects.create(name="Room 2.1") + + def test_rooms_page_renders_filter_form(self): + response = self.client.get(self.rooms_url) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'method="get"', html=False) + self.assertContains(response, 'name="name"', html=False) + + def test_rooms_page_without_name_filter_returns_all_rooms(self): + response = self.client.get(self.rooms_url) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.matching_room_one.name) + self.assertContains(response, self.matching_room_two.name) + self.assertContains(response, self.non_matching_room.name) + + def test_rooms_page_filters_rooms_by_partial_name(self): + response = self.client.get(self.rooms_url, {"name": "Room 1"}) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.matching_room_one.name) + self.assertContains(response, self.matching_room_two.name) + self.assertNotContains(response, self.non_matching_room.name) + + def test_rooms_page_filters_rooms_case_insensitively(self): + response = self.client.get(self.rooms_url, {"name": "room 1"}) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.matching_room_one.name) + self.assertContains(response, self.matching_room_two.name) + self.assertNotContains(response, self.non_matching_room.name) + + def test_rooms_page_with_non_matching_name_returns_no_rooms(self): + response = self.client.get(self.rooms_url, {"name": "Suite"}) + + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, self.matching_room_one.name) + self.assertNotContains(response, self.matching_room_two.name) + self.assertNotContains(response, self.non_matching_room.name) + + def test_rooms_page_ignores_whitespace_only_name_filter(self): + response = self.client.get(self.rooms_url, {"name": " "}) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.matching_room_one.name) + self.assertContains(response, self.matching_room_two.name) + self.assertContains(response, self.non_matching_room.name) + + def test_rooms_page_preserves_name_filter_in_form(self): + response = self.client.get(self.rooms_url, {"name": "Room 1"}) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["form"]["name"].value(), "Room 1") From 32f422508b499a83e260a757a7b7fafdb4085f06 Mon Sep 17 00:00:00 2001 From: Daniel Socas Date: Wed, 1 Apr 2026 06:24:53 +0100 Subject: [PATCH 5/5] feat(rooms): add empty state and result ordering to room filter --- pms/templates/rooms.html | 3 +++ pms/views.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pms/templates/rooms.html b/pms/templates/rooms.html index b38949c8e..bd6d0cea2 100644 --- a/pms/templates/rooms.html +++ b/pms/templates/rooms.html @@ -26,5 +26,8 @@

Habitaciones del hotel

+ +{% empty %} +

No se encontraron habitaciones.

{% endfor %} {% endblock content%} diff --git a/pms/views.py b/pms/views.py index 8da7b356f..dd1344ab0 100644 --- a/pms/views.py +++ b/pms/views.py @@ -239,7 +239,7 @@ def get(self, request, pk): class RoomsView(View): def get(self, request): form = RoomFilterForm(request.GET or None) - rooms_qs = Room.objects.all() + rooms_qs = Room.objects.all().order_by("name", "id") if form.is_valid(): name = (form.cleaned_data.get("name") or "").strip()