似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

巳年じゃないけど Python やろうぜ(その 20)

Ajax の活用(続き)

バリデーションエラーの出力

フォームのバリデーションエラーに関する情報は form.errors に辞書形式で格納されているので、それを利用すれば行けることがわかりました。

views.py

from django.shortcuts import render
from django.http.response import JsonResponse
from django.utils.translation import ugettext as _
from .forms import CustomerForm
from .models import Customer

# Create your views here.


def index(request):
    form = CustomerForm(label_suffix='')
    return render(request, 'customer_search/search.html', {
        'form': form,
    })


def search(request):
    form = CustomerForm(request.POST)
    if form.is_valid():
        customer_id = request.POST['customer_id']
        try:
            customer = Customer.objects.get(pk=customer_id)
            customer_name = customer.name
            customer_phone = customer.phone
        except Customer.DoesNotExist:
            customer_name = _('No data!')
            customer_phone = _('No data!')
    else:
        customer_name = _('Error!')
        customer_phone = _('Error!')
    return JsonResponse({
        'customer_name': customer_name,
        'customer_phone': customer_phone,
        'errors': form.errors,
    })

メソッド名が微妙に変わってるので、対応して urls.py も修正しておきます。

from django.conf.urls import url
from . import views

app_name = 'customer_search'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^search/$', views.search, name='search'),
]

templates/customer_search/search.html

{% load static i18n %}
{% trans 'customer search' as page_title %}
<!doctype html>
<html>
  <head>
    <title>{{ page_title | title }}</title>
    <link rel="stylesheet" href="{% static 'customer_search/css/search.css' %}">
    <script src="//code.jquery.com/jquery-3.2.0.min.js"></script>
    <script src="{% static 'customer_search/js/js.cookie.js' %}"></script>
    <script src="{% static 'customer_search/js/csrf_token.js' %}"></script>
  </head>
  <body>
    <h1>{{ page_title | title }}</h1>
    <form method="post" action="{% url 'customer_search:search' %}">
      {% csrf_token %}
      <table>
        {% for field in form %}
          <tr>
            <th>{{ field.label_tag }}</th>
            <td>
              {{ field }}
              <span class="error" id="{{ field.html_name }}_errors"></span>
            </td>
          </tr>
        {% endfor %}
      </table>
      <input type="submit" value="{% trans 'Search' %}">
    </form>
    <script src="{% static 'customer_search/js/search.js' %}"></script>
  </body>
</html>

span 要素は DOM で追加する方がスマートかなと思ったけど、やってみると意外に苦戦した*1ので、とりあえずこうしておきます。

static/customer_search/js/search.js

$('form').submit(function (event) {
  event.preventDefault();
  $.post(
    $('form').attr('action'),
    { 'customer_id': $('#id_customer_id').val() },
    function (res) {
      $('#id_customer_name').val(res.customer_name);
      $('#id_customer_phone').val(res.customer_phone);
      var errors = res.errors;
      var messages = [];
      if(!$.isEmptyObject(errors)) {
        messages = errors.customer_id;
      }
      $('#customer_id_errors').text(messages.join(''));
    }
  );
});

エラーメッセージはフォームのフィールド名ごとに配列で格納されているので、join で連結*2します。

画面遷移が発生していないことが確認できます。

*1:自分が下手な書き方をしたせいでエラーメッセージが消えずにどんどん増えていくという間抜けなことにw

*2:今回のケースでは複数のエラーメッセージが格納されるケースは確認できていないので、必要ないと言えば必要ないのですがw