Django:使用会话数据管理多值POST表单字段,表单验证重定向后未传递列表

iovurdzv  于 2023-08-08  发布在  Go
关注(0)|答案(1)|浏览(107)

我的Django应用程序中有一个高级搜索表单,我想使用POST方法。使用GET会暴露出太多关于应用程序内部工作的信息。使用POST需要一些额外的工作来确保分页功能正确。
我找到了很多相关的答案:Paginating the results of a Django forms POST request
我决定将POST查询存储在会话数据中,以确保它可用于分页。我将整个POST作为QueryDict存储在会话中,如下所示:https://www.abidibo.net/blog/2014/09/19/paginating-results-django-form-post-request/
由于某种原因,在表单验证成功并随后重定向之后,会话数据将丢失所有列表,并仅保留列表中的最后一个值作为单个项。
简化模型(实际模型要复杂得多):

class Organisation(models.Model):
    name = Charfield()
    location = Charfield()
    industry = Charfield() # This is a choices field with 19 options
...

个字符
我有两个视图驱动搜索-搜索表单视图和搜索结果视图。

class OrganisationSearchFormView(FormView):

    class Meta:
        model = Organisation

    form_class = OrganisationSearchForm
    template_name = "organisation_search_form.html"
    success_url = reverse_lazy("organisation_search_results")

    def post(self, request):
        form = OrganisationSearchForm(request.POST)
        if form.is_valid():
            request.session['post-query'] = self.request.POST
            return self.form_valid(form)
        else:
            return self.form_invalid(form)    
...
class OrganisationSearchResultListView(ListView):
    model = Organisation
    template_name = "organisation_search_results.html"

    def get(self, request, *args, **kwargs):
        # if there is not post query in the session data, redirect user back to the search form
        if 'post-query' not in request.session:
            return HttpResponseRedirect(reverse_lazy("organisation_search"))
        return super().get(request, *args, **kwargs)

    def get_queryset(self, **kwargs):
        qs = super().get_queryset()
        post_query = QueryDict('').copy()
        post_query.update(self.request.session.get('post-query'))

        search_org = post_query.get('search_org')
        location = post_query.get('location')
        search_industry = post_query.getlist('search_industry') # .getlist is required for a multivalue object.

        qs = Organisation.objects.all()

        # I filter the queryset based on the query parameters

的字符串
这里的工作流是表单视图提交(POST方法)给它自己进行验证。如果表单有效,则QueryDict对象存储在会话数据中。随后,会自动重定向(GET方法)到搜索结果视图。
表单视图->表单视图(验证)->结果视图
在重定向过程中,QueryDict中的任何列表都被剥离为单个值。我已经在表单视图post方法的表单验证块和results视图的get方法中放置了一个print语句,它显示了更改:

app-web-1  | [2023-07-17 15:31:03 +0000] [7] [DEBUG] POST /advsearch/i/
app-web-1  | <QueryDict: {'csrfmiddlewaretoken': ['************'], 'search_org': [''], 'search_industry': ['11', '21', '22', '23'], 'location': ['']}>
app-web-1  | [2023-07-17 15:31:04 +0000] [7] [DEBUG] GET /sr/i/
app-web-1  | <QueryDict: {'csrfmiddlewaretoken': ['************'], 'search_org': [''], 'search_industry': ['23'], 'location': ['']}>


我认为这个问题与dict的使用有关,而不是QueryDict在该链中的某个地方使用:django QueryDict only returns the last value of a list
如果我将表单操作更改为POST直接到搜索结果视图,我不会遇到同样的问题,列表会保留并出现在会话数据中。即表单视图->结果视图
但是,我认为直接发布到结果视图,表单验证步骤被忽略了。是这样吗?
解决这个问题的最佳方法是什么?是否有一种方法可以使用:表单视图->表单视图(验证)->结果视图工作流?
任何建议或指导都是赞赏的。

eqfvzcg8

eqfvzcg81#

我遇到了一个类似的问题,并制定了一个解决方案,我认为可能对你的情况也有帮助。
总的来说:

  • 当你将request.POST保存到request.session时,django会序列化它,这个过程会导致request.POST中包含多个值的字段被截断为一个值。根据示例,'search_industry': ['11', '21', '22', '23']变为'search_industry': ['23']
  • 为了避免在序列化过程中丢失数据,您可以编写自己的函数,在调用request.session.save()之前序列化request.POST中的任何多值字段。
  • 在下一个视图中从会话中获取发布的数据时,您必须对任何多值字段的值进行反序列化。
  • 序列化和反序列化函数都需要知道request.POST中的哪些键与多值字段相关联。为此,我在表单中添加了一个隐藏的输入,该表单列出了所有的多项选择字段。

下面是我在requset.POST中序列化和反序列化多值字段的函数:

def serialize_requestPOST_multivalue_items(requestPOST):  # pass in request.POST
    new_requestPOST = requestPOST.copy()  # a mutable QueryDict

    for key in requestPOST.getlist("multi_value_keys"):
        value = json.dumps(requestPOST.getlist(key))
        new_requestPOST.__setitem__(key, value)
    
    # The value of new_requestPOST["multi_value_keys"] also needs to be serialized.
    new_requestPOST.__setitem__(
        "multi_value_keys", json.dumps(requestPOST.getlist("multi_value_keys"))
    )

    return new_requestPOST

def deserialize_requestPOST_multivalue_items(requestPOST):
    requestPOST["multi_value_keys"] = json.loads(requestPOST["multi_value_keys"])

    for key in requestPOST["multi_value_keys"]:
        requestPOST[key] = json.loads(requestPOST[key])

    return requestPOST

字符串
当你想要选择将request.POST保存到request.session时,下面是一个可以添加到表单中的mixin:

class SerializerPrereqMixin:
    def add_multi_values_hidden_field(self):
        multi_value_keys = [
            field_key for field_key, field in self.fields.items() 
            if isinstance(
                field.widget, (SelectMultiple, CheckboxSelectMultiple)
            )
        ]
        self.fields["multi_value_keys"] = CharField(
            widget=MultipleHiddenInput(),
            required=False,
            initial=multi_value_keys,
        )


有了这个,在第一个视图中,你会这样:

requestPOST = serialize_requestPOST_multivalue_items(request.POST)
request.session["posted_data"] = requestPOST
request.session.save()


在要使用已发布数据的视图中:

posted_data = request.session.get("posted_data")
posted_data = deserialize_requestPOST_multivalue_items("posted_data")

相关问题