Как минимизировать поисковый запрос django?
Я планирую реализовать функцию автопоиска в django! Но я считаю, что переборщил, поэтому мне нужно предложение:
В основном эта поисковая строка находится на странице, где сначала все доступные данные отображаются в виде списка! А затем, когда пользователь набирает что-то, я отфильтровываю это с помощью jquery autocomplete.
In the above, am making two queries one to get all data and another one on each user types
Поскольку это занимает два запроса, я считаю, что это неэффективно
Here is my search function
def search_query(request):
if 'term' in request.GET:
qs = University.objects.filter(name__icontains=request.GET.get('term'))
names = list()
for data in qs:
names.append(data.name)
return JsonResponse(names, safe=False)
Вышеуказанное вызывается каждый раз, когда пользователь набирает текст в строке поиска, и это мой код jquery autocomplete:
<script>
$( function() {
$( "#tags" ).autocomplete({
source: "{% url 'students:search-query' %}" // this hits the above def
});
} );
</script>
Кроме этого, я также делаю это, чтобы показать полный список в качестве исходных данных,
def index(request):
univesity = University.objects.filter(published=True)
context = {'univesity': univesity}
return render(request, 'students/search/index.html', context)
правильно ли это или есть какой-нибудь эффективный способ? Можно ли объединить оба запроса или, пожалуйста, предложите эффективное решение (сейчас нужно перебрать около 50 000 данных, может увеличиться)
Во-первых, оба запроса возвращают разные данные. Индекс возвращает только опубликованные университеты, а поиск - все.
Есть два решения вашей проблемы. Любое из них должно работать. Выбирайте тот, с которым вам удобнее работать (т.е. если вы пишете только на Python, улучшайте запрос, если предпочитаете JS, делайте это на фронтенде)
Выполнение поиска на фронтенде
Теперь, поскольку вы возвращаете все данные на фронтенд, вы можете просто передать все это автозаполнению Jquery в качестве источника и позволить всему этому происходить на самом фронтенде.
Это может быть просто что-то вроде
<script>
var universities = [] // populated either via an API or HTML rendering
$( function() {
$( "#tags" ).autocomplete({
source: universities.map(uni => uni.name),
});
});
</script>
ИЛИ
<script>
var universities = [] // populated either via an API or HTML rendering
function filterUniversities(request, response) {
var filteredNames = universities.filter(uni => uni.name === request).map(uni => uni.name);
response(filteredNames);
}
$( function() {
$( "#tags" ).autocomplete({
source: filterUniversities,
});
});
</script>
В любом случае сначала нужно задать список университетов. Глядя на ваш код, я вижу, что вы выводите данные, поэтому об API не может быть и речи. Вы можете сделать что-то вроде
<script>
var universities = [{% for uni in universities %}{{ uni }}{% if not forloop.last %}, {% endif %}{% endfor %}]
</script>
Вам может понадобиться заменить часть {{ uni }}
соответствующим кодом.
Существует также нечто под названием json_script, что я никогда не пробовал, но, возможно, оно работает лучше.
{{ universities|json_script:"university-data" }}
И тогда ваш сценарий просто станет
<script>
var universities = JSON.parse(document.getElementById('university-data').textContent);
$( function() {
$( "#tags" ).autocomplete({
source: universities.map(uni => uni.name),
});
});
</script>
Первый вариант замедлит начальное время рендеринга, поскольку фронтенд должен вычислить этот список в самом начале.
Второй сделает каждый поиск медленным.
Конечно, обе эти задержки исчисляются в мс и должны быть намного меньше, чем обращение к серверу.
Улучшение запроса бэкенда
Если вы предпочитаете сохранять поиск асинхронным и вызывать метод поискового запроса каждый раз, вы можете увеличить задержку перед тем, как auto complete выполнит вызов. По умолчанию он ожидает 300 мс после того, как пользователь перестал набирать текст, прежде чем выполнить поиск.
С точки зрения фактического запроса
def search_query(request):
term = request.GET.get('term')
if term:
names = list(
University.objects
.filter(name__icontains=request.GET.get('term'))
.values_list("name", flat=True)
)
return JsonResponse(names, safe=False)
Основными изменениями здесь являются:
- Мы получаем из базы данных только названия университетов. Каждый университет может содержать много данных, и выборка всех данных может быть медленной. Если мы получаем только то, что необходимо, это будет намного быстрее.
- Мы приводим набор запросов к списку, а не создаем новый список с помощью append. Выполнение append в цикле - самый медленный способ создания списка. List comprehensions намного быстрее, но в данном случае нам это даже не нужно. Простое приведение к списку - самый быстрый способ.
Мы также помещаем термин в переменную и проверяем его. Не очень большой прирост скорости, но если термин пустой (т.е. request.GET['term'] = ''
), мы не хотим выполнять поиск. 50k строк не должно быть много для любой базы данных.
Если вы по-прежнему считаете, что база данных работает слишком медленно, вам нужно проанализировать ваши запросы и посмотреть, как их можно оптимизировать. Возможно, потребуется какой-то индекс, но это выходит за рамки данного ответа и, скорее всего, не понадобится.