2026, Jan 11 09:02

Как исправить ошибку Cannot use None в Django Q: разделяем форму и результаты поиска чатов

Почему в Django возникает ошибка Cannot use None as a query value при фильтрации через Q, и как исправить: разделите форму поиска и страницу результатов.

Искать групповые чаты по названию вроде бы просто, но из‑за мелочи в обработке запроса всё может сломаться ещё до того, как пользователь что‑то введёт. Ниже — короткое объяснение проблемы и рабочий способ избежать ошибки «Cannot use None as a query value», сохраняя для пользователя понятный сценарий.

Краткое описание проблемы

Нужно отфильтровать групповые чаты по текстовому запросу с помощью выражения Q в Django. Ошибка возникает сразу при загрузке страницы, потому что представление выполняет фильтрацию в БД, когда параметр запроса ещё отсутствует.

Модель и представление, воспроизводящие проблему

Ниже — модель и представление для демонстрации. Идентификаторы простые и понятные, логика программы сохранена без изменений.

# models.py
from django.db import models
from django.contrib.auth.models import User
import shortuuid
class GroupRoom(models.Model):
    room_key = models.CharField(max_length=128, unique=True, default=shortuuid.uuid)
    room_title = models.CharField(max_length=128, null=True, blank=True)
    avatar = models.ImageField(upload_to='uploads/profile_pictures', default='uploads/profile_pictures/default.png', blank=True)
    bio = models.TextField(max_length=500, blank=True, null=True)
    owner = models.ForeignKey(User, related_name='owned_rooms', blank=True, null=True, on_delete=models.SET_NULL)
    online_users = models.ManyToManyField(User, related_name='present_in_rooms', blank=True)
    participants = models.ManyToManyField(User, related_name='joined_rooms', blank=True)
    private_flag = models.BooleanField(default=False)
    def __str__(self):
        return self.room_key
# views.py
from django.views import View
from django.shortcuts import render
from django.db.models import Q
from .models import GroupRoom
class RoomLookup(View):
    def get(self, request, *args, **kwargs):
        term = self.request.GET.get('chat-query')
        rooms = GroupRoom.objects.filter(
            Q(room_key__room_title__icontains=term)
        )
        payload = { 'rooms': rooms }
        return render(request, 'chat/search.html', payload)

Что происходит под капотом

Страница открывается GET‑запросом без строки запроса. Представление всё равно запускает фильтрацию QuerySet и передаёт отсутствующее значение как None. Django отклоняет его в lookup и выбрасывает указанную пользователем ошибку. Корневая причина в том, что обращение к базе выполняется безусловно при первоначальной загрузке, ещё до ввода поискового термина.

использование представления на классах запускало запрос при открытии страницы. Мне пришлось сделать отдельную страницу только с полем ввода, а результаты использовать на другой странице

Этого небольшого изменения достаточно, чтобы поиск в базе не выполнялся, пока нет валидного ввода.

Решение: разделите страницу ввода и страницу результатов

Разделите сценарий на два представления. Первое рендерит форму поиска без обращений к базе. Второе отвечает за результаты и запускает фильтрацию только при наличии параметра запроса.

# views.py
from django.views import View
from django.shortcuts import render
from django.db.models import Q
from .models import GroupRoom
class RoomSearchPage(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'chat/search_form.html')
class RoomSearchResults(View):
    def get(self, request, *args, **kwargs):
        term = request.GET.get('chat-query')
        if not term:
            return render(request, 'chat/results.html', { 'rooms': GroupRoom.objects.none() })
        rooms = GroupRoom.objects.filter(
            Q(room_key__room_title__icontains=term)
        )
        return render(request, 'chat/results.html', { 'rooms': rooms })

В такой структуре первый GET, который рендерит форму, не делает запросов. Фильтр вычисляется только на эндпойнте результатов и только при переданном терме, так что значение None вовсе не участвует.

Почему это важно

Защита логики фильтрации от отсутствующих входных данных предотвращает лишние исключения и снимает ненужную нагрузку при загрузке страницы. К тому же путь пользователя становится ясным: сначала ввести запрос, затем увидеть отфильтрованные групповые чаты. Такой UX поиска предсказуем, а поведение на сервере — однозначно.

Итоги

Запуск фильтрации в базе без проверки входных данных приведёт к ошибкам вроде «Cannot use None as a query value». Держите форму поиска и обработку результатов раздельно, чтобы фильтр срабатывал только при осмысленном вводе. Этого небольшого шага достаточно, чтобы стабилизировать представление и добиться ожидаемого поведения поиска по названиям чатов.