在Django中有没有不干扰的JavaScript编写和AJAX表单提交的惯用方法?

10 投票
3 回答
1297 浏览
提问于 2025-04-18 01:06

我现在是一个Ruby/Rails开发者,正在一家使用Python/Django的公司工作。我开始对Python有些兴趣,但在某些我认为重要的方面,觉得Django和Rails相比还有些不够。我的很多当前和未来的工作都将集中在向我们的API发送AJAX请求上。作为Rails开发者,我会使用不干扰的JavaScript,特别是在表单提交时,会添加一个data-remote标签,如下所示。

然后我会在控制器中写一个方法来处理这个请求,并在/assets/js目录下的JS文件中写一个JavaScript/jQuery函数,使用事件委托来处理客户端的响应。我原以为在Django中也会有类似的方式来实现这种功能。

我想说的是,我以为Django会提供类似Rails的“魔法”,这样我就不需要每次想要发送AJAX请求时都写jQuery AJAX函数。我写了一个粗略的比较(非常粗略),展示了我在Rails和Django中是如何写这些代码的。我想知道这种在Django中的做法是否不正确。我知道StackOverflow不是用来发表个人意见的,但我认为有些原则是适用于任何语言或框架的,比如通过不重复写AJAX函数来保持代码的简洁,这并不是个人意见,而是打破了一个被广泛接受的规则。

我目前在Django中处理AJAX请求的方法感觉不太对,或者说我可能只是习惯了Rails通过data-remote="true"属性提供的“魔法”。希望能得到一些指导,帮助我找到一个更好的方法,谢谢。

RAILS

views/some_controller/form.html.erb

<form action="<%= endpoint %>" method="post" data-remote="true" id="form">
  FORM FIELDS HERE
</form>

assets/javascripts/some_model.js

$('body').on('ajax:success', '#form', function(event, data) {
  DO SOME STUFF HERE
});

controllers/some_controller.rb

def some_ajax_action
  if request.xhr?
    THIS IS AN AJAX REQUEST RENDER A VIEW PARTIAL & 
    MANIPULATE THE DOM WITH JS OR RESPOND WITH JSON
  else
    THIS ISNT AN AJAX REQUEST
  end
end

DJANGO

some_app/templates/form.html

<form action="{% url 'app:view' %}" method="post" id="form">
   FORM FIELDS HERE OR {{ BUILD_FORM_FROM_CONTEXT }} 
</form>

some_app/static/assets/js/some_app.js

$("#form").on("submit", function(event) {
  $.ajax({
    type: "POST",
    beforeSend: function (request) {
      request.setRequestHeader("X-CSRFToken", csrftoken);
    },
    data: data,
    url: "endpoint",
    dataType: 'json',
    contentType: 'application/json',
    }).done(function(data) {
      cb(null, data)
    }).fail(function(data) {
      cb(data)
    }).always(function(data) {
      cb(data)
    })
  });
});

3 个回答

1

我觉得Django本身没有什么特别的说法。不过,有一些可以重复使用的应用程序,它们提供了很多常用的实现。

比如,Crispy forms可以简化Django的ajax表单。这样在请求之前就不需要处理csrf了。下面是他们的教程中的一个例子:

var example_form = '#example-form';

$.ajax({
    url: "{% url 'save_example_form' %}",
    type: "POST",
    data: $(example_form).serialize(),
    success: function(data) {
        if (!(data['success'])) {
            // Here we replace the form, for the
            $(example_form).replaceWith(data['form_html']);
        }
        else {
            // Here you can show the user a success message or do whatever you need
            $(example_form).find('.success-message').show();
        }
    },
    error: function () {
        $(example_form).find('.error-message').show()
    }
});
1

在Django中,处理这个问题并不是特别简单,不过如果你使用一些额外的库,可以让处理ajax表单提交变得更容易。

CsrfProtection.js - 这个库会把csrf令牌添加到你的ajax请求头中。

(function() {
  var csrfSafeMethod, sameOrigin;

  csrfSafeMethod = function(method) {
    return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);
  };

  sameOrigin = function(url) {
    var host, origin, protocol, sr_origin;
    host = document.location.host;
    protocol = document.location.protocol;
    sr_origin = '#' + host;
    origin = protocol + sr_origin;
    return (url === origin || url.slice(0, origin.length + 1) === origin + '/') || (url === sr_origin || url.slice(0, sr_origin.length + 1) === sr_origin + '/') || !(/^(\/\/|http:|https:).*/.test(url));
  };

  $.ajaxSetup({
    beforeSend: function(xhr, settings) {
      if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
        return xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
      }
    }
  });

}).call(this);

接着,你需要引入一个叫做 jquery.form.js 的JavaScript库。使用这个库后,你可以做到类似下面这样的事情:

$('#form_id').ajaxSubmit({
    success: function(response) {console.log(response);},
    error: function(response) {console.log(response);}
});

虽然这样可能没有你在Ruby中列出的那么简单,但它确实减少了csrf令牌和表单序列化的重复工作。

8

你的问题的答案是否定的。Django 对 AJAX 没有什么特别的做法,它对 AJAX 没有明确的看法,尤其是在前端部分。

后端的结构比较统一,因为它使用了基于类的视图(CBV)。在 CBV 中,通常会看到一个简单的 AJAXResponseMixin,这个混合类利用了所有通用视图通过一个方法 get_context_data 可靠地生成上下文的特点。这个混合类可以拿到上下文,然后把它转换成 JSON 格式。

Django 并没有强制要求使用特定的 AJAX 模式;你只需要按照自己的方式来构建即可。

如果你喜欢你在 Rails 示例中看到的做法,那就照做吧。

这个练习的目的是让你明白,这个想法其实涉及的框架很少。只需要几行代码。

首先,让我们用你提到的语法来重现你的 data-remote=True 标签和事件监听系统。请注意,这里都是伪代码。

$(function() {
    $("[data-remote=true]").each(function(index, el) {
        $(el).submit(function(ev) {
            ev.preventDefault(); // disable default form submit. We are using ajax.
            $.ajax({
                url: $(this).attr('action') || '',
                data: $(this).serialize(),
                method: $(this).attr('method') || 'get',
                success: function(response) {
                    // on success, trigger an event with the response data
                    $(el).trigger('ajax:success', [response]);
                },
                error: function(xhr) {
                    $(el).trigger('ajax:error', [xhr]);
                }
            })
            return false;
        })
    })
})

完成了。现在让我们在模板中使用它:

<form id="my-django-form" method="POST" data-remote="true" action="some-controller">
    {{ form.as_p }}
    <input type="submit" />
</form>

很好,现在加一些 JS 来响应这个表单的提交:

$(function() {
    $('body').on('ajax:success', '#my-django-form', function(event, data) {
        alert("My barebones ajax framework was successful!", data);
    })
    $('body').on('error', '#my-django-form', function(event, data) {
        alert("Whoops, error!");
    })
})

控制器 / Django 视图:

def some_ajax_action(request):
    """ Some basic AJAX Handler.
    """
    if request.is_ajax():
        return http.HttpResponse(json.dumps({
            'this-is': 'some-json',
        }))
    else:
        print "This isn't an ajax request"

关于如何将其应用为框架和可重用模式的想法,包括许多框架常见的部分渲染。

class AJAXFrameworkMixin(object):
    """ A more advanced, reusable pattern for class based views.
    Perhaps also allowing partial rendering via custom header `RENDER_PARTIAL` used by the jQuery request.
    """
    def dispatch(self, request, *args, **kwargs):
        if request.is_ajax():
            partial = request.META.get('X_RENDER_PARTIAL')
            if partial:
                return self.handle_ajax_partial(request, partial)
            return self.handle_ajax(request)

        return super(AJAXFrameworkMixin, self).dispatch(request, *args, **kwargs)

    def handle_ajax(self, request):
        """ Ajax specific handler.
        Convert this view context into JSON.
        """
        ctx = self.get_context_data()
        return http.HttpResponse(json.dumps(ctx))

    def handle_ajax_partial(self, request, partial):
        """ Given a render partial header from the barebones AJAX framework, render said partial with context.
        """
        t = template.loader.get_template(partial)
        return t.render(template.RequestContext(request, self.get_context_data()))

为了完成这个例子,我们需要修改最初的 jQuery 脚本,以根据一个新的数据属性(比如 data-partial-name)来设置一个头部信息。

现在,我们的简单框架可以通过 HTML 数据属性调用基于类的视图中的特定方法。也就是说,设置 data-partial="some-template-name.html" 会触发 YourView.handle_ajax_partial,它将返回渲染后的 HTML。

如果 data-partial 被设置,你还可以通过为你的 data-remote 函数添加一个默认处理程序,来自动化这个调用的渲染和更新。

撰写回答