Разметка результатов запроса POST для Django
Я использую Django Forms для выполнения фильтрации/фасетного поиска через POST, и мне хотелось бы, чтобы класс Django paginator организовал результаты. Как сохранить исходный запрос при передаче клиента между различными страницами? Другими словами, кажется, что я теряю данные POST, как только передаю запрос GET для другой страницы обратно к моим представлениям. Я видел некоторые рекомендации по использованию AJAX для обновления только блока результатов страницы, но мне интересно, есть ли для этого механизм Django.
Спасибо.
Ответы
Ответ 1
Если вы хотите получить доступ к данным хранилища в более позднем запросе, вам придется его где-то хранить. Django предоставляет несколько способов архивирования:
1) Вы можете использовать sessions, чтобы сохранить запрос: Каждый посетитель, посетивший ваш сайт, получит пустой объект сеанса, и вы можете хранить все, что хотите внутри этого объекта, который действует как dict. Недостаток: один посетитель не может выполнять множественный поиск с разбивкой по страницам одновременно.
2) Использовать файлы cookie:. Если вы установите cookie, который хранится на стороне клиента, браузер добавит данные cookie к каждому запросу, к которому вы можете получить доступ. Файлы cookie более дружественны к серверу, потому что для них на сервере не нужен диспетчер сеансов, но данные, хранящиеся в файлах cookie, видны (и редактируются) клиенту. Недостаток: тот же, что и раньше.
3) Используйте скрытые поля:. Вы можете добавить форму с некоторыми скрытыми полями на странице результатов поиска и сохранить запрос внутри них. Затем клиент отправляет запрос при отправке формы. Недостаток: вы должны использовать форму с кнопками отправки для разбивки на страницы на странице (простые ссылки не работают).
4) Создайте ссылки, содержащие запрос: Вместо использования POST вы также можете использовать GET. Например, у вас может быть ссылка типа "/search/hello+world/?order=votes"
и "paginated links", например "/search/hello+world/2/?order-votes"
. Затем запрос можно легко получить из URL-адреса. Недостаток: максимальный объем данных, которые вы можете отправлять через GET, ограничен (но это не должно быть проблемой для простого поиска).
5) Используйте комбинацию. Вы можете захотеть сохранить все данные в сеансе или базе данных и получить к ним доступ через сгенерированный ключ, который вы можете поместить в URL. URL-адреса могут выглядеть так: "/search/029af239ccd23/2"
(для 2-й страницы), и вы можете использовать ключ для доступа к огромному количеству данных, которые вы сохранили ранее. Это устраняет недостаток решения 1, а также решение 4. Новое недостаток: много работы:)
6) Использовать AJAX: С помощью ajax вы можете хранить данные внутри некоторых js-переменных на стороне клиента, которые затем могут передаваться другим запросам. И поскольку ajax будет обновлять список результатов, переменные не теряются.
Ответ 2
Читая очень хороший ответ от tux21b, я решил реализовать первый вариант, то есть использовать сеанс для хранения запроса. Это приложение, которое ищет базы данных недвижимости. Вот код представления (используя django 1.5):
def main_search(request):
search_form = UserSearchForm()
return render(request, 'search/busca_inicial.html', {'search_form': search_form})
def result(request):
if request.method == 'POST':
search_form = UserSearchForm(request.POST)
if search_form.is_valid():
# Loads the values entered by the user on the form. The first and the second
# are MultiChoiceFields. The third and fourth are Integer fields
location_query_list = search_form.cleaned_data['location']
realty_type_query_list = search_form.cleaned_data['realty_type']
price_min = search_form.cleaned_data['price_min']
price_max = search_form.cleaned_data['price_max']
# Those ifs here populate the fields with convenient values if the user
# left them blank. Basically the idea is to populate them with values
# that correspond to the broadest search possible.
if location_query_list == []:
location_query_list = [l for l in range(483)]
if realty_type_query_list == []:
realty_type_query_list = [r for r in range(20)]
if price_min == None:
price_min = 0
if price_max == None:
price_max = 100000000
# Saving the search parameters on the session
request.session['location_query_list'] = location_query_list
request.session['price_min'] = price_min
request.session['price_max'] = price_max
request.session['realty_type_query_lyst'] = realty_type_query_list
# making a query outside the if method == POST. This logic makes the pagination possible.
# If the user has made a new search, the session values would be updated. If not,
# the session values will be from the former search. Of course, that is what we want because
# we want the 'next' and 'previous' pages correspond to the original search
realty_list_result = FctRealtyOffer.objects.filter(location__in=request.session['location_query_list']
).filter(price__range=(request.session['price_min'], request.session['price_max'])
).filter(realty_type__in=request.session['realty_type_query_lyst'])
# Here we pass the list to a table created using django-tables2 that handles sorting
# and pagination for us
table = FctRealtyOfferTable(realty_list_result)
# django-tables2 pagination configuration
RequestConfig(request, paginate={'per_page': 10}).configure(table)
return render(request, 'search/search_result.html', {'realty_list_size': len(realty_list_result),
'table': table})
Надеюсь, что это поможет! Если у кого есть какие-либо улучшения, предлагайте, пожалуйста.
Ответ 3
Как @rvnovaes, способ использовать сеанс для решения вопроса.
Недостатком его решения является то, что если есть много полей поиска, вам нужно написать много строк кода, а также, если вы покажете форму поиска на странице результатов, все поля будут пустыми, а они должны сохранить их значения.
Поэтому я предпочел бы сохранить все данные сообщения в сеансе, а в начале представления принудительно введите значение request.POST и request.method, если будет определен сеанс:
""" ... """
if not request.method == 'POST':
if 'search-persons-post' in request.session:
request.POST = request.session['search-persons-post']
request.method = 'POST'
if request.method == 'POST':
form = PersonForm(request.POST)
request.session['search-persons-post'] = request.POST
if form.is_valid():
id = form.cleaned_data['id']
""" ... """
Подробнее здесь
Ответ 4
Вы можете запросить объект запроса, если он ajax, просто request.is_ajax
. Таким образом, вы можете определить, будет ли он сначала отправлять запрос или дополнительные вопросы о следующих страницах.
Ответ 5
Отобразите форму поиска и результаты на одном шаблоне django. Сначала используйте css, чтобы скрыть область отображения результатов. При отправке формы вы можете проверить, не нашли ли поиск какие-либо результаты и скрыть форму поиска с помощью css, если результаты существуют. Если результатов не существует, используйте css, чтобы скрыть область отображения результатов, как раньше. В ваших ссылках на страницы используйте javascript для отправки формы, это может быть так же просто, как document.forms[0].submit(); return false;
Вам нужно будет обработать, как передать номер страницы движку подкачки django.
Ответ 6
Я сделал это в своем веб-приложении с параметрами get. Может быть, я могу вам помочь:
Views.py
class HomeView(ListView):
model = Hotel
template_name = 'index.html'
paginate_by = 10 # if pagination is desired
def get_queryset(self):
qs = super().get_queryset()
kwargs = {}
if 'title' in self.request.GET:
title = self.request.GET.get('title')
if title != '':
kwargs['title__icontains'] = title
if 'category' in self.request.GET:
category = self.request.GET.get('category')
if category:
kwargs['category_id'] = category
if 'size' in self.request.GET:
size = self.request.GET.get('size')
if size:
kwargs['size_id'] = size
if 'service' in self.request.GET:
service = self.request.GET.get('service')
if service:
kwargs['service_id'] = service
if 'ownership' in self.request.GET:
ownership = self.request.GET.get('ownership')
if ownership:
kwargs['ownership_id'] = ownership
qs = qs.filter(**kwargs)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form_init = {}
form = SearchForm()
if self.request.GET.items():
try:
parameters = self.request.GET.items()
except KeyError:
parameters = {}
for key, value in parameters:
for field in form.fields:
if key == field:
form_init[key] = value
form.initial = form_init
if 'title' in self.request.GET:
title = self.request.GET.get('title')
if title != '':
context.update({
'title': title
})
if 'category' in self.request.GET:
category = self.request.GET.get('category')
context.update({
'category': category
})
if 'size' in self.request.GET:
size = self.request.GET.get('size')
context.update({
'size': size
})
if 'service' in self.request.GET:
service = self.request.GET.get('service')
context.update({
'service': service
})
if 'ownership' in self.request.GET:
ownership = self.request.GET.get('ownership')
context.update({
'ownership': ownership
})
context.update({
'search_form': form
})
return context
Файл пагинации HTML
<div class="row">
{% if is_paginated %}
<nav aria-label="...">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?category={{category}}&size={{size}}&service={{service}}&ownership={{ownership}}&page={{ page_obj.previous_page_number }}">Previous</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">Previous</span></li>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?category={{category}}&size={{size}}&service={{service}}&ownership={{ownership}}&page={{ page_obj.next_page_number }}">Next</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
Ответ 7
Мое предложение было бы сохранить запрос на публикацию, используя сеанс или cookie. В случае, если данные поста являются конфиденциальными, вы должны использовать сеанс для их хранения. Приведенный ниже код содержит мою логику для реализации его с помощью сеанса.
def index(request):
is_cookie_set = 0
# Check if the session has already been created. If created, get their values and store it.
if 'age' in request.session and 'sex' in request.session:
age = request.session['age']
sex = request.session['sex']
is_cookie_set = 1
else:
# Store the data in the session object which can be used later
request.session['age'] = age
request.session['sex'] = sex
if(request.method == 'POST'):
if(is_cookie_set == 0): # form submission by the user
form = EmployeeForm(request.POST)
sex = form.cleaned_data['sex']
age = form.cleaned_data['age']
if form.is_valid():
result = Employee.objects.all(sex=sex,age_gte=age) # filter all employees based on sex and age
else: # When the session has been created
result = Employee.objects.all(sex=sex,age_gte=age)
paginator = Paginator(result, 20) # Show 20 results per page
page = request.GET.get('page')
r = paginator.get_page(page)
response = render(request, 'app/result.html',{'result':result})
return response
else:
form = EmployeeForm()
return render(request,'app/home.html',{'form':form})
Вы должны также проверить, являются ли поля сообщения пустыми или нет, и изменить логику в соответствии с этим. Вы также можете сохранить весь почтовый запрос в сеансе, как предложено @abidibo.
Вы также можете использовать куки для того же. Я объяснил это здесь