Быстрый и грязный Django — Передача данных в JavaScript без AJAX

Автор оригинала: Radoslav Georgiev
  • Перевод

Привет, хабровчане. Для будущих студентов курса "Web-разработчик на Python" подготовили перевод материала.


Если мы хотим передать данные из Django в JavaScript, мы обычно говорим об API, сериализаторах, вызовах JSON и AJAX. Обычно дело усложняется наличием React или Angular на фронте.

Но иногда вам нужно сделать что-то быстро и грязное – построить диаграмму и не задумываться над инфраструктурой одностраничного приложения.

Обычный подход

Допустим, у нас есть приложение на Django со следующей моделью:

from django.db import models


class SomeDataModel(models.Model):
    date = models.DateField(db_index=True)
    value = models.IntegerField()

И простой TemplateView:

<img alt="Изображение выглядит как текст

from django.views.generic import TemplateView


class SomeTemplateView(TemplateView):
    template_name = 'some_template.html'

Теперь мы можем построить простую линейную диаграмму с помощью Chart.js, и мы не хотим использовать AJAX, создавать новые API и т.д.

Если мы хотим визуализировать простую линейную диаграмму в нашем some_template.html, код будет выглядеть следующим образом (взято из этих примеров):

<canvas id="chart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<script>
window.onload = function () {
  var data = [48, -63, 81, 11, 70];
  var labels = ['January', 'February', 'March', 'April', 'May'];

  var config = {
    type: 'line',
    data: {
      labels: labels,
      datasets: [
        {
          label: 'A random dataset',
          backgroundColor: 'black',
          borderColor: 'lightblue',
          data: data,
          fill: false
        }
      ]
    },
    options: {
      responsive: true
    }
  };

  var ctx = document.getElementById('chart').getContext('2d');
  window.myLine = new Chart(ctx, config);
};
</script>

И получится следующее:

Теперь, если мы захотим построить диаграмму данных, поступающих из SomeDataModel, мы подойдем к этой задаче следующим образом:

from django.views.generic import TemplateView

from some_project.some_app.models import SomeDataModel


class SomeTemplateView(TemplateView):
    template_name = 'some_template.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context['data'] = [
            {
                'id': obj.id,
                'value': obj.value,
                'date': obj.date.isoformat()
            }
            for obj in SomeDataModel.objects.all()
        ]

        return context

А затем мы визуализируем массив JavaScript с помощью шаблона Django:

<canvas id="chart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<script>
window.onload = function () {
  // We render via Django template
  var data = [
    {% for item in data %}
      {{ item.value }},
    {% endfor %}
  ]

  // We render via Django template
  var labels = [
    {% for item in data %}
      "{{ item.date }}",
    {% endfor %}
  ]

  console.log(data);
  console.log(labels);

  var config = {
    type: 'line',
    data: {
      labels: labels,
      datasets: [
        {
          label: 'A random dataset',
          backgroundColor: 'black',
          borderColor: 'lightblue',
          data: data,
          fill: false
        }
      ]
    },
    options: {
      responsive: true
    }
  };

  var ctx = document.getElementById('chart').getContext('2d');
  window.myLine = new Chart(ctx, config);
};

</script>

Именно так это и работает, но, как по мне, слишком грязно. У нас больше нет JavaScript, но есть JavaScript с шаблоном Django. Мы теряем возможность выделить JavaScript в отдельный файл .js. Также мы не можем красиво работать с этим JavaScript.

Но можем сделать лучше и быстрее.

Добавим остроты

Стратегия следующая:

  1. В нашем случае мы будем сериализовать данные через json.dumps и хранить их в контексте.

  2. Отрендерим скрытый элемент <div> с уникальным id и атрибутом data-json, а именно с сериализованными данными JSON. 

  3. Запросите этот <div> из JavaScript, прочитайте атрибут data-json и используйте JSON.parse, чтобы получить необходимые данные.

Преимуществом становится то, что код на JavaScript не содержит шаблонов Django, но имеет переиспользуемый шаблон для получения необходимых данных. 

Почти как упрощенный AJAX.

Ниже пример того, как я использую эту стратегию:

import json

from django.views.generic import TemplateView


class SomeTemplateView(TemplateView):
    template_name = 'some_template.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context['data'] = json.dumps(
            [
                {
                    'id': obj.id,
                    'value': obj.value,
                    'date': obj.date.isoformat()
                }
                for obj in SomeDataModel.objects.all()
            ]
        )

        return context

Теперь извлечем наш код на JavaScript в статичный файл chart.js.

В результате получим some_template.html:

{% load static %}

<div style="display: none" id="jsonData" data-json="{{ data }}"></div>

<canvas id="chart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<script src="{% static 'chart.js' %}"></script>

Как видите в скрытом div происходит магия. Мы скрываем div, чтобы удалить его из любой верстки. Здесь вы можете дать ему соответствующий id или использовать любой подходящий HTML-элемент.

Атрибут data-json (который необязателен и не является чем-то предопределенным) содержит нужный нам JSON.

Теперь, наконец, мы реализуем следующую простую функцию для получения и анализа необходимых нам данных:

function loadJson(selector) {
  return JSON.parse(document.querySelector(selector).getAttribute('data-json'));
}

И вот наш chart.js готов:

function loadJson(selector) {
  return JSON.parse(document.querySelector(selector).getAttribute('data-json'));
}

window.onload = function () {
  var jsonData = loadJson('#jsonData');

  var data = jsonData.map((item) => item.value);
  var labels = jsonData.map((item) => item.date);

  console.log(data);
  console.log(labels);

  var config = {
    type: 'line',
    data: {
      labels: labels,
      datasets: [
        {
          label: 'A random dataset',
          backgroundColor: 'black',
          borderColor: 'lightblue',
          data: data,
          fill: false
        }
      ]
    },
    options: {
      responsive: true
    }
  };

  var ctx = document.getElementById('chart').getContext('2d');
  window.myLine = new Chart(ctx, config);
};

Быстрый и грязный способ для тех, кто просто хочет что-то отправить. А вот и конечный результат:

Дисклеймер 

Я не рекомендую вам пользоваться этим подходом для чего-то большего, чем грязное доказательство концепции. Как только ваш код на JavaScript начнет расти, станет сложно его контролировать. Рекомендуется пройти путь SPA с одним из популярных фреймворков (в нашем случае React).


Узнать подробнее о курсе "Web-разработчик на Python".

Посетить Demo day к курсу.

OTUS
Цифровые навыки от ведущих экспертов

Комментарии 1

    +3

    Ну зачем в атрибутах-то? Начиная с версии 2.1 есть же json_script


    {{ value|json_script:"hello-data" }}

    И получаем:


    <script id="hello-data" type="application/json">{"hello": "world"}</script>

    Результат получаем как:


    const value = JSON.parse(document.getElementById('hello-data').textContent);

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое