Django - 如何处理前端的ajax form submit 请求?通过json返回处理结果,不要重定向(redirect)

问题背景

在web应用开发中,经常遇到需要诸如用户注册提交个人信息,或者录入商品信息的需求。在浏览器上一般以一个Form表单来包含用户所有需要输入的控件。那么浏览器需要在用户输入完所有信息之后, 将表单里面的相关数据传送给后台,完成数据的录入。

Form submit

在火狐的开发者教程中,对Form submit有很好的说明和解释 mozilla's sending and retrieving form data 。 在文中有提到, form表单中的action定义了表单数据将会被发往什么地方。说到这里, 就不得不提一下 Post/Redirect/Get

PRG都被上升到了防止表单重复提交数据的一种Web设计模式。 说的是应用在比如像在电商提交订单这种场景下,总不能让客户重复提交订单吧。字面意思是,后端在收到用户提交的表单数据,并在正确处理之后, 应该让前端重定向到另外一个URL。 就笔者看来,虽然并不能完全理解,但总之对于什么设计模式, 什么最佳实践,还是应该心存敬畏,能遵从的就遵从。

Ajax submit

对于使用ajax来提交form表单, 请先看一下以下链接。

  • https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript
  • https://scotch.io/tutorials/submitting-ajax-forms-with-jquery#getting-started
  • https://code.tutsplus.com/tutorials/submit-a-form-without-page-refresh-using-jquery--net-59
    Ajax提交用户表单和传统Form submit提交有以下重要区别:

    Ajax提交是浏览器在后台发送请求至服务器, 也是在后台接受到服务器返回的结果再做处理;
    Form submit处理是在提交数据之后, 后台返回重定向命令, 浏览器再收到之后,去访问命令中指明的URL。

    另外Ajax是部分刷新浏览器, 无法处理重定向命令。

    问题: Reverse for *** not found. *** is not a valid view function or pattern name.

    说了这么多, 是因为笔者在Django后台处理ajax上传的表单数据的时候, 心心念念的要实现重定向/PRG。
    在使用redirect方法的时候,出现了该问题,在网上搜索了一会,发现大家比较少遇到该问题,主要搜索到的信息是在template中使用url的时候相关的信息。

  • urls.py
  • urlpatterns = [
        url(r'^$', views.SkuView.as_view(), name='sku'),
        # download sku template
        url(r'^download/$', views.download, name='download'),
        # sku import / add
        url(r'^skuimport/$', views.skuimport.as_view(), name='skuimport'),
        #  book query 
        url(r'^book/query/$', views.books.as_view(), name='books'),
        # add book 
        url(r'^add/$', views.addBook.as_view(), name='addBook')
    
  • views.py
  • class addBook(generic.View):
        def get(self, request):
            if request.method == "GET":
                print "addBook get GET reqeuest"
                dumpRequest(request)
                return render(request, 'p_bookAdd.html');
        def post(self, request):
            if request.method == "POST":
                error = ''
                success = ''
                dumpRequest(request)
                form = bookAddForm(request.POST)
                if form.is_valid():
                    isbn = form.cleaned_data['isbn']
                    if Book.objects.filter(isbn = isbn).exists():
                        error = "ISBN %d book exist !" %(isbn)
                    else:
                        success = "True"
                        b = Book(**form.cleaned_data)
                        b.save()
                    resp = {
                        'success': success,
                        'error': error
                    return redirect('addBook')
    

    Django报错的信息如下:

    Internal Server Error: /sku/add/
    Traceback (most recent call last):
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/core/handlers/exception.py", line 41, in inner
        response = get_response(request)
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/core/handlers/base.py", line 187, in _get_response
        response = self.process_exception_by_middleware(e, request)
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/core/handlers/base.py", line 185, in _get_response
        response = wrapped_callback(request, *callback_args, **callback_kwargs)
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/views/generic/base.py", line 68, in view
        return self.dispatch(request, *args, **kwargs)
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/views/generic/base.py", line 88, in dispatch
        return handler(request, *args, **kwargs)
      File "/home/fall4u/PycharmProjects/Django-metronic-learn/sku/views.py", line 111, in post
        return redirect('addBook')
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/shortcuts.py", line 56, in redirect
        return redirect_class(resolve_url(to, *args, **kwargs))
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/shortcuts.py", line 147, in resolve_url
        return reverse(to, args=args, kwargs=kwargs)
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/urls/base.py", line 91, in reverse
        return force_text(iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)))
      File "/usr/local/lib/python2.7/dist-packages/Django-1.11.9-py2.7.egg/django/urls/resolvers.py", line 497, in _reverse_with_prefix
        raise NoReverseMatch(msg)
    NoReverseMatch: Reverse for 'addBook' not found. 'addBook' is not a valid view function or pattern name.
    

    其实报错原因已经指出的很明显呢, 就是redirect的参数不对, 看起来应该不是addBook。
    但是让人疑惑的是官方文档给出的例子就是如此,没有更多其他信息。

    将redirect的参数改为0return redirect('sku:addBook'),看来还是命名空间的问题。

    Django 处理ajax form 请求

    言归正传, Django处理form请求, 一般按照以下步骤处理

  • 验证form数据请求是否合法(is_valid)
  • 处理form数据,比如保持数据库什么的
  • 构造json数据返回
  • def post(self, request):
        if request.method == "POST":
            error = ''
            success = ''
            form = bookAddForm(request.POST)
            if form.is_valid():
                isbn = form.cleaned_data['isbn']
                if Book.objects.filter(isbn = isbn).exists():
                    error = "ISBN %d book exist !" %(isbn)
                else:
                    success = "True"
                    b = Book(**form.cleaned_data)
                    b.save()
                resp = {