对Django表单POST请求结果进行分页

20
我正在使用 Django Forms 通过 POST 进行筛选/分面搜索,并且我想使用 Django 的 paginator 类来组织结果。在将客户端传递到各个页面时,如何保留原始请求?换句话说,当我将 GET 请求传递给视图的另一页时,似乎会立即丢失 POST 数据。我看到一些建议使用 AJAX 刷新页面的结果块,但我想知道是否有一种 Django 原生机制可以实现这一点。谢谢。
8个回答

28

如果您想在后续请求中访问存储的数据,那么您需要将其存储在某个地方。Django提供了几种方法来实现此目的:

1) 您可以使用 sessions 来存储查询结果:每个访问您站点的访客都会获得一个空的session对象,您可以将任何想要存储的内容存储在其中,它就像一个字典一样。缺点是:单个访客无法同时进行多个带有分页的搜索。

2) 使用cookies:如果您设置了一个保存在客户端的cookie,浏览器将在每次请求时将cookie的数据附加到请求中,您可以访问这些数据。与服务器比较友好的是,对于它们,您不需要在服务器上使用会话管理器,但在cookie中存储的数据对客户端可见(且可编辑)。缺点与前者相同。

3) 使用隐藏字段:您可以在搜索结果页面上添加一个带有一些隐藏字段的表单,并在其中存储查询内容。然后,客户端在提交表单时会重新发送查询。缺点是:您必须在页面上使用带有提交按钮的表单来进行分页(简单链接不起作用)。

4) 创建包含查询的链接:您可以使用GET而不是POST。例如,您可以创建像 "/search/hello+world/?order=votes" 这样的链接,以及像 "/search/hello+world/2/?order-votes" 这样的“分页链接”。然后,可以轻松地从URL中检索查询内容。缺点是:通过GET发送的数据量受到限制(但对于简单的搜索来说这应该不是问题)。

5) 使用组合方式:您可以将所有数据存储在会话或数据库中,并通过生成的键在URL中访问它们。 URL可能看起来像 "/search/029af239ccd23/2" (用于第2页),您可以使用键来访问之前存储的大量数据。这消除了解决方案1和解决方案4的缺点。新的缺点是:需要更多工作 :)

6) 使用AJAX:使用AJAX,您可以将数据存储在客户端的一些js变量中,然后将其传递给其他请求。由于AJAX只会更新您的结果列表,因此变量不会丢失。


谢谢,这很有帮助。只是想更详细地解释一下这个问题:这是否是分页器类的预期用途?我的视图处理初始搜索表单,然后将paginator.page()对象发送到模板的第一页。结果列表是从该页面的object_list生成的。我觉得很奇怪,我不能发送整个搜索结果集,并在不为每个页面重新提交搜索的情况下翻页。如果这是该类的预期用途,那么我可以使用它。只是想确保我没有错过什么明显的东西。谢谢! - andyashton
1
是的,这就是预期的使用方式。不要忘记Django是一个Web框架,而Web请求本质上是无状态的。因此,如果您想保持状态,您必须将其存储在某个地方 - tux21b已经为您提供了一些选项。 - Daniel Roseman

8

阅读了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})

希望能对您有所帮助!如果有任何改进建议,请随时提出。


6

像@rvnovaes所说的,使用session是解决此问题的一种方式。

他的解决方案的缺点是,如果有许多搜索字段,您必须编写许多代码行,而且如果在结果页面中显示搜索表单,则所有字段都将为空,而它们应该保留其值。

因此,我更愿意将所有帖子数据保存在会话中,并在视图开始时强制设置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']
""" ... """

更多信息 在此处


对于基于函数的视图,运行得非常好。 - Soren

2

我在我的Web应用程序中使用了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>

0
以下代码是可行的,第一个请求是 GET 请求,它访问表单,将直接转到 else 块。 一旦用户提出搜索查询,将显示结果,这将是一个 post 请求,并且第二个 if 块将被激活,我们将在会话中存储此请求。 当用户访问第二个搜索页面时,它将是一个 GET 请求,但我们正在检查是否存在活动分页会话,以及是否不是 GET 的页面请求。此时将触发第一个 if 块。
def search(request):
    if not request.method == "POST" and 'page' in request.GET:
    if 'search-query' in request.session:
        request.POST = request.session['search-query']
        request.method = 'POST'

    if request.method == 'POST':
    form = Search_form(request.POST)
    request.session['search-query'] = request.POST
    if form.is_valid():
        search_query = form.cleaned_data.get('search_query')
        search_parameter = form.cleaned_data.get('search_parameter')
        print(search_query, search_parameter)
        queryset_list = CompanyRecords.objects.filter(**{f'{search_parameter}__icontains': search_query}).exclude(
            company_name__isnull=True).exclude(description__isnull=True).exclude(phones__isnull=True).exclude(
            emails__isnull=True)[:5]
        page = request.GET.get('page', 1)
        paginator = Paginator(queryset_list, 2)

        try:
            queryset = paginator.page(page)
        except PageNotAnInteger:
            queryset = paginator.page(1)
        except EmptyPage:
            queryset = paginator.page(paginator.num_pages)

        return render(request, 'search/search_results.html', {'queryset': queryset})

    else:
    context = {
        'form': Search_form()
    }
    return render(request, 'search/search.html', context)

0

我的建议是使用会话或cookie存储POST请求。如果POST数据很敏感,您应该使用会话来存储它。下面的代码包含了我使用会话实现它的逻辑。

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建议的那样将整个帖子请求存储在会话中。

你也可以使用cookies来实现相同的功能。我在这里解释了它。


0
您可以询问请求对象是否是ajax,方法是 request.is_ajax。这样您就可以检测它是第一次POST请求还是有关下一页的进一步问题。

0

在一个单一的Django模板上显示搜索表单和结果。最初,使用CSS隐藏结果显示区域。在提交表单后,您可以检查搜索是否返回任何结果,并使用CSS隐藏搜索表单(如果存在结果)。如果没有结果,则像以前一样使用CSS隐藏结果显示区域。在分页链接中,使用JavaScript提交表单,这可能只是简单地使用document.forms[0].submit(); return false;

您需要处理如何将页面编号传递给Django的分页引擎。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接