Статьи

АХАХ с Джанго и JQuery

Недавно меня спросили об использовании AJAX через jQuery с Django, и я упомянул, что я часто использую фрагменты html и декоратор, чтобы добавить функциональность Ajax в существующие представления. Посмотрим, как это работает.

У меня есть существующее представление, которое я хочу обновить через AJAX. Давайте сделаем простейшую возможную вещь:

from django.shortcuts import render_to_response
from django.template import RequestContext

counter = [0]

def index(request):
    counter[0] = counter[0] + 1
    return render_to_response("index.html",
                              {'counter': str(counter[0])},
                        context_instance=RequestContext(request))

Моя функция представлений отображает шаблон и увеличивает константу каждый раз при загрузке представления. Я знаю, я знаю, мой счетчик сбрасывается, если мой сервер перезагружается, и я не защищен от потоков, но эй — это всего лишь пример! Представление отображается двумя шаблонами:

index.html

{% extends "base.html" %}
{% block mytext %}
Current counter value is {{ counter }}.
{% endblock %}

и base.html

<html>
  <head>
    <title>Simple Demo</title>
  </head>
  <body>
  <h1>This is a simple page</h1>
  <div id="replace_me">
    {% block mytext %}
    This is dynamic content that should be replaced.
    {% endblock %}
  </div>
  </body>
</html> 

Каждый раз, когда я перезагружаю страницу, я вижу что-то вроде: у

нас есть начальная настройка кейса, давайте сделаем это AJAX! Нет, если подумать, давайте сделаем это АХАХ! Технически AJAX расшифровывается как асинхронный Javascript и XML, но в последнее время я редко использую XML. Если мне действительно нужен формат обмена данными, я обычно использую JSON — но я также часто использую асинхронный Javascript и шаблон HTML. Я думаю, что это выходит AJAH, но AHAH определенно веселее сказать.

Техника очень проста — частью моей страницы, которая должна загружаться асинхронно, можно управлять, просто загрузив фрагмент HTML и вставив его вместо обмена данными и используя Javascript для перестройки страницы. Обычно это меньше кода (особенно Javascript) и имеет преимущество использования одинаковых представлений на стороне сервера и, надеюсь, одинаковых шаблонов. Он также встроен в мой любимый Javascript Framework — так что давайте посмотрим на него в действии.

Сначала я собираюсь добавить библиотеку jQuery в мой базовый шаблон и JS и html, необходимые для запуска асинхронной перезагрузки. Теперь мой шаблон выглядит так:

<html>
  <head>
    <title>Simple Demo</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
      $(function(){
          $("#click_me").click(function(){
              $("#replace_me").load("/ #replace_me");
          });
      });
  </script>
  </head>
  <body>
  <h1>This is a simple page</h1>
  <div id="replace_me">
    <div>
    {% block mytext %}
    This is dynamic content that should be replaced.
    {% endblock %}
    </div>
  </div>
  <a id="click_me" href="#">Click Me</a>
  </body>
</html>  

Я не собираюсь подробно объяснять javascript — но даже неопытные jQueryists могут видеть, что я добавил функцию, вызванную нажатием моей ссылки, и функция использует встроенную функцию jQuery .load (), чтобы сделать асинхронный вызов URL «/ ». Я также указываю селектор CSS, чтобы загруженная страница (что эквивалентно обновлению текущей страницы) анализировалась, и содержимое первого div внутри #replace_me вставлялось в div #replace_me текущей страницы. Мой номер меняется без перезагрузки браузера! Woohoo!

Но давайте сделаем это лучше и немного сложнее. Может быть, мы не хотим отображать всю страницу каждый раз, потому что наш базовый шаблон выполняет сложные вещи, такие как отображение вошедшего в систему пользователя, создание меню, создание боковой панели недавних изменений и так далее. Мы бы хотели, чтобы каждый асинхронный вызов создавал только динамические данные, которые меняются.

Для этого мы можем воспользоваться вспомогательным методом объекта запроса Django, который называется .is_ajax (). Это, в свою очередь, зависит от использования нормального браузера или платформы Javascript, но в нашем случае jQuery гарантирует, что заголовок «X-REQUESTED-WITH» отправляется с каждым асинхронным запросом. Код представления теперь читает:

from django.shortcuts import render_to_response
from django.template import RequestContext

counter = [0]

def index(request):
    counter[0] = counter[0] + 1
    if request.is_ajax():
        template = "index_ajax.html"
    else:
        template = "index.html"
    return render_to_response(template,
                              {'counter': str(counter[0])},
                        context_instance=RequestContext(request))

Мой новый шаблон index_ajax.html содержит только интересующий нас фрагмент, а мой старый index.html был изменен, чтобы включить его:

 

index_ajax.html

Current counter value is {{ counter }}.

и index.html

{% extends "base.html" %}
{% block mytext %}
{% include "index_ajax.html" %}
{% endblock %}

Я также изменяю шаблон base.html, чтобы отбрасывать селектор css в моем вызове .load (). Просмотр запросов в firebug подтверждает, что каждый раз включается только тот фрагмент текста, который мне нужен, но страница работает как прежде — при первой загрузке страницы в обычном режиме и нажатии на ссылку запускается асинхронный вызов, который возвращает только нужный нам фрагмент и вставляет его на родительскую страницу.

base.html

<html>
  <head>
    <title>Simple Demo</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" type="text/javascript"></script>
    <script type="text/javascript">
      $(function(){
          $("#click_me").click(function(){
              $("#replace_me").load("/");
          });
      });
  </script>
  </head>
  <body>
  <h1>This is a simple page</h1>
  <div id="replace_me">
    {% block mytext %}
    This is dynamic content that should be replaced.
    {% endblock %}
  </div>
  <a id="click_me" href="#">Click Me</a>
  </body>
</html>  

Теперь все работает, как я хочу, но мы все еще можем немного очистить наш код. Я обычно использую декоратор из django-раздражает, чтобы дать себе хороший ярлык render_to. Вы можете прочитать код, чтобы увидеть, как он работает, но использовать его просто — либо укажите свой шаблон в обращении к декоратору render_to, либо верните его в ключе «TEMPLATE» в dict, который возвращает ваше представление — render_to будет обрабатывать генерацию request_context и рендеринг вашего шаблона для вас. Теперь мой вид выглядит следующим образом (со связанным декоратором, скопированным в decorators.py рядом с моим views.py):

from django.shortcuts import render_to_response
from django.template import RequestContext
from decorators import render_to

counter = [0]

@render_to()
def index(request):
    counter[0] = counter[0] + 1
    if request.is_ajax():
        template = "index_ajax.html"
    else:
        template = "index.html"
    return {'TEMPLATE': template, 'counter': str(counter[0])}

намного приятнее и все работает как раньше. Однако для меня это настолько распространенный шаблон, что я помещаю логику выбора шаблонов в сам декоратор сразу после того, как он выдает переменную TEMPLATE:

    tmpl = output.pop('TEMPLATE', template)
    if request.is_ajax():
        if "AJAX_TEMPLATE" in output:
            tmpl = output.pop("AJAX_TEMPLATE")

Это позволяет мне просто указать два моих шаблона в моем возвращенном dict, и правильный будет автоматически выбран. В последний раз для views.py:

from django.shortcuts import render_to_response
from django.template import RequestContext
from decorators import render_to

counter = [0]

@render_to()
def index(request):
    counter[0] = counter[0] + 1
    return {'TEMPLATE': 'index.html',
            'AJAX_TEMPLATE': 'index_ajax.html',
            'counter': str(counter[0])}

And we’re done. A single line of Javascript (more or less) enables our Asyncronous call and at the price of one more template we can alternately render fragments or the whole page for our view with the decorator cleaning up our view code and handling the template choosing for us.