From 93c09d478de728e01ace4e6b261a19d2192641cf Mon Sep 17 00:00:00 2001 From: sapuri Date: Mon, 17 Aug 2026 16:41:53 +0900 Subject: [PATCH 1/2] fix UnboundLocalError in search view when query exceeds max_length When SearchForm validation failed (e.g. q longer than 100 chars), the else branch never assigned q but the context dict referenced it, raising UnboundLocalError and returning HTTP 500. Restructure the view to a linear flow: resolve q in one place, derive is_blank from q, and inline the single-use search_items helper so the invalid form now renders the blank search page. --- main/tests/test_views.py | 4 ++++ main/views/search.py | 25 +++++++------------------ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/main/tests/test_views.py b/main/tests/test_views.py index 6771cd5e..c06cecce 100644 --- a/main/tests/test_views.py +++ b/main/tests/test_views.py @@ -23,6 +23,10 @@ def test_get(self): resp = self.client.get(resolve_url(f'{APP_NAME}:search')) self.assertEqual(200, resp.status_code) + def test_get_with_too_long_query(self): + resp = self.client.get(resolve_url(f'{APP_NAME}:search'), {'q': 'a' * 101}) + self.assertEqual(200, resp.status_code) + class LevelSelectTests(TestCase): def test_get(self): diff --git a/main/views/search.py b/main/views/search.py index fc88f681..fdc256c2 100644 --- a/main/views/search.py +++ b/main/views/search.py @@ -8,31 +8,20 @@ def search(request: HttpRequest) -> HttpResponse: """ 検索結果 """ - def search_items(q: str = None) -> list[Music]: - filter_params = {} - if q: - # 大文字小文字区別無しの部分一致 - filter_params['title__icontains'] = q - return Music.objects.filter(**filter_params).order_by('level', '-sran_level', 'title') - search_form = SearchForm(request.GET) - if search_form.is_valid(): - q = search_form.cleaned_data['q'] - if q: - is_blank = False - items = search_items(q=q) - else: - is_blank = True - items = [] + q = search_form.cleaned_data['q'] if search_form.is_valid() else '' + + if q: + # 大文字小文字区別無しの部分一致 + items = Music.objects.filter(title__icontains=q).order_by('level', '-sran_level', 'title') else: - is_blank = True items = [] context = { - 'title': '{q} の検索結果'.format(q=q) if not is_blank else '楽曲検索', + 'title': '{q} の検索結果'.format(q=q) if q else '楽曲検索', 'search_form': search_form, 'q': q, 'items': items, - 'is_blank': is_blank + 'is_blank': not q } return render(request, 'main/search.html', context) From 1d099bdedd0e94754b6fdb011443921d92307df1 Mon Sep 17 00:00:00 2001 From: sapuri Date: Mon, 17 Aug 2026 16:46:13 +0900 Subject: [PATCH 2/2] render an unbound form when search query is invalid An invalid query (e.g. over max_length) previously kept the bound form, echoing the raw value back into the input. Re-bind to an empty form so invalid input renders the same blank search page as an empty query. Also assert the blank-state context in the regression test. --- main/tests/test_views.py | 3 +++ main/views/search.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/main/tests/test_views.py b/main/tests/test_views.py index c06cecce..67bf1ea8 100644 --- a/main/tests/test_views.py +++ b/main/tests/test_views.py @@ -26,6 +26,9 @@ def test_get(self): def test_get_with_too_long_query(self): resp = self.client.get(resolve_url(f'{APP_NAME}:search'), {'q': 'a' * 101}) self.assertEqual(200, resp.status_code) + self.assertTrue(resp.context['is_blank']) + self.assertEqual('', resp.context['q']) + self.assertEqual([], resp.context['items']) class LevelSelectTests(TestCase): diff --git a/main/views/search.py b/main/views/search.py index fdc256c2..29241bf2 100644 --- a/main/views/search.py +++ b/main/views/search.py @@ -9,7 +9,12 @@ def search(request: HttpRequest) -> HttpResponse: """ 検索結果 """ search_form = SearchForm(request.GET) - q = search_form.cleaned_data['q'] if search_form.is_valid() else '' + if search_form.is_valid(): + q = search_form.cleaned_data['q'] + else: + # 無効な入力は空検索と同じ扱いにする (入力値も再描画しない) + search_form = SearchForm() + q = '' if q: # 大文字小文字区別無しの部分一致