在Django中有没有不干扰的JavaScript编写和AJAX表单提交的惯用方法?
我现在是一个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 个回答
我觉得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()
}
});
在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令牌和表单序列化的重复工作。
你的问题的答案是否定的。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
函数添加一个默认处理程序,来自动化这个调用的渲染和更新。