在 Django 中是不是有一种惯用的方法来编写不显眼的 JavaScript 和/或进行 AJAX 表单提交?

Posted

技术标签:

【中文标题】在 Django 中是不是有一种惯用的方法来编写不显眼的 JavaScript 和/或进行 AJAX 表单提交?【英文标题】:Is there an idiomatic approach in Django for writing unobtrusive JavaScript and/or making AJAX form submissions?在 Django 中是否有一种惯用的方法来编写不显眼的 JavaScript 和/或进行 AJAX 表单提交? 【发布时间】:2014-05-14 14:26:24 【问题描述】:

我是一名 Ruby/Rails 开发人员,现在在一家 Python/Django 商店工作。我已经开始对 Python 热身,但是,在我认为重要的某些方面,我仍然很难找到与 Rails 相媲美的 Django。我当前和未来的很多工作都将集中在向我们的 API 发出 AJAX 请求。作为一名 Rails 开发人员,我会使用不显眼的 javascript,特别是在提交表单时添加了一个 data-remote 标记,如下所示。

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

我想我真正想说的是,我认为 Django 会为 Rails 提供类似的“魔力”,因为每次我想发出 AJAX 请求时都不必写出 jQuery AJAX 函数。我写了一个粗略的比较(非常粗略),我将如何写出这两个。我想知道这是否是我在 Django 中的 Rails 中所做的不正确的方法。我知道 *** 不是用来发表意见的,但我认为无论你使用什么语言/框架都适用的打破原则,即通过不一遍又一遍地写出 AJAX 函数来干掉代码,并没有真正违背意见,这更像是打破公认的规则。

我目前在 Django 中处理 AJAX 请求的方法感觉不对,或者我可能只是习惯了通过 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)
    )
  );
);

【问题讨论】:

“有没有一种惯用的方式来将这个 Ruby/Rails 成语表达为惯用的 Python/Django?”这是一个完全可以接受的问题。它表明您已经完成了研究。 你看过这个了吗? docs.djangoproject.com/en/dev/ref/request-response/… 嘿,杰森,我看过那些文档。这是处理调用是否为 AJAX 的好方法。但我认为我的问题更多是关于是否存在类似于 Rails 使用 UJS 的惯用方法。我找到了一个 Django-UJS 库,但它似乎没有得到维护。将data-remote="true" 添加到 html 表单或链接会通过 AJAX 发送请求。我在想也许模板过滤器可以用来做同样的事情。我会做更多的研究并找出答案。 AFAIK 大多数 Rails “魔法”来自 Rails 提供的 JavaScript 插件(在您的示例中为 rails.js)。 Django 只专注于 Python,并没有提供太多的内置模板和开箱即用的静态文件(除了在像 admin 这样的成熟解决方案中),所以你需要以简单的方式来做。不过,如果您想自己实现 extra template tags and django.js 来封装这些内容,那么没有什么能阻止您。 @evkline:您能否就所提供的答案提供一些反馈?它将帮助我奖励赏金。如果您已经以不同的方式解决了问题,您可以写下您的解决方案作为答案。 【参考方案1】:

您的问题的答案是否定的。 Django 没有惯用的 AJAX 方法。它对 AJAX 没有意见,尤其是在前端。

由于基于类的视图 (CBV) 的结构方式,后端倾向于遵循更相似的模式;通常看到一个简单的AJAXResponseMixin 混合到 CBV 中,它利用了所有通用视图通过单一方法get_context_data 可靠地生成其上下文的事实。 mixin 可以只获取所述上下文并将其转换为 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 函数添加默认处理程序来自动呈现/更新所述调用。

【讨论】:

【参考方案2】:

这不是在 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);

然后包含 javascript 库 jquery.form.js。使用此库将允许您执行以下操作:

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

它可能不像您在 Ruby 中列出的那样简单,但它确实减少了 csrf 令牌和表单序列化的重复。

【讨论】:

【参考方案3】:

我不相信 django 本身有一个成语。但是,有一些可重用的应用程序确实提供了非常常用的实现。

例如,Crispy 表单可以简化 django ajax 表单。无需在请求之前进行任何 csrf 处理。来自他们tutorial 的示例:

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 中是不是有一种惯用的方法来编写不显眼的 JavaScript 和/或进行 AJAX 表单提交?的主要内容,如果未能解决你的问题,请参考以下文章

Django 1.8 和 Django Crispy Forms:是不是有一种简单易行的方法来实现日期选择器?

有没有一种惯用的方法来避免长的 Clojure 字符串文字?

惯用/快速 Django ORM 检查 mysql/postgres 上是不是存在

比较文件版本是不是有一种不那么复杂的方法?

从 Golang 中的数组中选择元素的最惯用方法?

Django在数据库中找到最旧的条目