Пишем простые проекты. Проект #1. Часть #2

Продолжаем писать проект "Страница со статистикой отжимания" с помощью flask + google chart's.

В этой части дополним шаблон javascript'ом, продумаем и реализуем сохранение/загрузки истории.

Ранее (http://bit.ly/20cxo8i) создан базовый проект flask и html файл с текстом Hiiii.

Структура файлов перед второй частью:

.
├── app.py
├── requirements.txt
└── templates
    └── index.html

Начинаем вторую часть.

Обращаю внимание что код в цикле этих статей очевидный и простой. Автор не ставит цели писать идеальный код.

Сначала разберемся какие данные есть в проекте и как будем их хранить.

В этом проекте нет разнообразия данных. Одна величина - количество отжиманий в момент времени T. Поэтому хранилище или крутая база данных не требуется - хватит и файла. В файл запишем список (массив) элементов из двух значений - время и количество отжиманий

Пример:

data = [
   (date1, value1),
   (date2, value2),
]

Дата будет в виде unixtime - т.е. количество секунд, прошедших с полуночи (00:00:00 UTC) 1 января 1970 года - т.е. целое число. Будем вычислять так:

import datetime
int(datetime.datetime.now().timestamp() * 1000)

Количество отжиманий тоже целое число.

Формат определен. Теперь начнем сохранять/загружать данные из файла. Для удобства будет использован модуль pickle из стандартной библиотеки.

Модуль pickle позволяет преобразовывать python-объект (переменную) в бинарный формат и обратно.

Это позволит не думать о способе хранения описанной структуры. Достаточно будет сформировать список кортежей, прогнать через модуль и сохранить в файл. Это и сделаем:

Далее страшный код с глобальной переменной :) Это аля домашнее задание - переписать код без глобальной переменной.

import pickle

DATA = []  # переменная в которой состояние сервера - список кортежей
PATH = 'store.pkl' # путь до файла, для сохранения состояния

# функция записи значения в файл, значение это (date1, value1) (кортеж, два элемента - целые числа)
def write(value):
    global DATA
    DATA.append(value)
    with open(PATH, 'wb') as fio:
        pickle.dump(DATA, fio)

# функция чтения данных
def read():
    global DATA
    try:
        with open(PATH, 'rb') as fio:
            DATA = pickle.load(fio)
    except FileNotFoundError:
        DATA = []

На что стоит обратить внимание:

  • модуль pickle. Этот проект пишем под Python3. Для Python2 необходимо написать cPickle
  • 'rb' и 'wb' - буква r означает read, w - write, а буква b - binary. Напомню, что pickle преоразует Python объект в бинарную структуру.

Практика: попрактикуйтесь с этим кодом - запишите данные с помощью функции write, прочитайте с помощью read

Справки: использование модуля six (ставится дополнительно) позволяет упростить написание переносимого кода. (т.е. совместимый с Python 2 и 3)

Поправим код запуска:

if __name__ == "__main__":
    read()
    app.run(debug=True)

Таким образом при запуске сервера в переменной DATA будет статистика отжиманий. Вспомним основную задачу проекта - отобразить статистику об отжиманиях на web-странице. Чтобы передать эти данные на страницу перепишем функцию index():

import json

@app.route("/")
def index():
    global DATA
    return render_template("index.html", data=json.dumps(DATA))

Что здесь интересного? В функцию render_template добавили аргумент data и передали данные в JSON формате. Название data играет роль. По этому имени будет доступны данные в html шаблоне.

Справка: Формат JSON подходит для web-проектов благодаря поддержке браузерами и простой структуре данных. Про JSON можно прочиать по ссылке

На данный момент с Python-кодом закончим и перейдем к шаблону. Теперь надо отобразить данные.

Заменив текст Hiiiiiiii на {{ data }}, как можно догадаться, получим отображение переменной json.dumps(DATA) (передали в методе index). Пока DATA пустая (нет файла с данными) можно присвоить какое-то свое значение - попробуйте.

Справка: формат записи {{ data }} это часть языка движка шаблонов Jinja2. Почитать возможности движка можно по ссылке. Подобный формат синтаксиса используется и в другом веб-фреймворке (django)

Теперь пойдем дальше, визуализируем данные.

Для этого будет использоваться Google charts. Это библиотека для Javascript, которая обладает хорошей документацией и примерами. Разговор про Javascript можно вести долго, поэтому приведу итоговый код и кратко опишу его.

<!DOCTYPE html>
<html lang="en">
<head>
    <script type="text/javascript"
            src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1.1','packages':['corechart', 'timeline']}]}"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
</head>
<body>

<div id="chart_div"></div>


<script>

    var dynamic_data = [];

    var json_data = {{ data|safe }};
    json_data.forEach(function (element) {
        dynamic_data.push([new Date(element[0]), element[1]]);
    });

    var chart = new google.visualization.LineChart(
            document.getElementById('chart_div'));

    google.load('visualization', '1', {packages: ['corechart']});
    google.setOnLoadCallback(drawChart);

    function genOptions() {
        return {
            height: 500,
            legend: {position: 'none'},
            enableInteractivity: false,
            title: 'Статистика отжиманий от пола',
            chartArea: {
                width: '85%'
            },
            hAxis: {
                title: 'Время',
                gridlines: {
                    count: -1,
                    units: {
                        days: {format: ['MMM dd']},
                        hours: {format: ['HH:mm', 'ha']}
                    }
                },
                minorGridlines: {
                    units: {
                        hours: {format: ['hh:mm:ss a', 'ha']},
                        minutes: {format: ['HH:mm a Z', ':mm']}
                    }
                }
            },
            vAxis: {
                title: 'Количество отжиманий',
                minValue: 0
            }
        };
    }

    function genData(dynamic_data) {
        var data = new google.visualization.DataTable();
        data.addColumn('datetime', 'X');
        data.addColumn('number', 'Отжимания');
        data.addRows(dynamic_data);
        return data;
    }

    function drawChart() {
        chart.draw(genData(dynamic_data), genOptions());
    }

</script>

</body>
</html>

В этом коде происходит следующее

  • В теге head подключаем библиотеки для рисования графиков
  • В блоке <div id="chart_div"></div>будет сам график
  • В блоке <script>...</script> описана инициализация графика (сделано форматирование осей и прочее)

А выглядит это все так:

Image

Итог на данный момент:

  • Есть flask приложение с возможностью сохранять и загружать данные в/из файла. Определен формат данных.
  • Есть страничка, на которой отображается график (пока без данных)

Остается связать эти две части вместе.

На этом прерву эту часть.


Продолжение следует.

Комментарии

Теги

notify, os, isinstance, encode, database, all, многопоточность, mail, срез, сборник, pytest, новый год, конкурентность, аргумент, gevent, pycon, GIL, python проект, интерпретатор, игры, замыкание, pynotify, функциональное программирование, pypi, типы данных, csv, график, testing, поиск ошибок, автоматизация, ardruio, swagger, память, vk.com, файл, pip, web, тест, статический анализ, dsl, syntax, отчет, notification, список, ide, графика, rest, pycallgraph, githook, generic, hook. webhook, история, asyncio, logging, инструмент, swig, click, Category: Полезные модули, crawler, while, aiohttp, оптимизация, clonedigger, Бизли, трансдьюсер, matplotlib, bottle, концепция, типизация, language, урок, fuzzy-testing, nose, image, путь, удаленный вызов процедур, тестирование, консоль, vk, c, list, pylint, operator, print, практика, сравнение, functools, польза, измерение, сопроцедуры, selenium, генерация данных, БД, мастер класс, plotly, bokeh, генерация, производительность, winapi, flask, typing, strip, lxml, os.path, grab, scandir, Qt, pycharm, проект, книга, dictwriter, coverage, html, фп, pypy, signal, стандартная библиотека, now, weakref, google, практика программирования, Tags:, corotine, sqlalchemy, nameko, синтаксис, import, паттер, virtualenv, api, зеленый поток, timeit, контекст, бд, funcy, encoding, кэш, json, статистика, байт-код, unittest, кодировка, datetime, opencv, ооп, itertools, package, fp, mixin, python, assert, pyqt, утка, garbage collector, frozenset, курс, numpy, django-debug-toolbar, терминал, xpath, closure, type, requirements, дубликат, статическая типизация, PIL, работа, debug, быстродействие, водяной знак, plot, рейтинг, ip, python3, yield from, fronteram scrapy, видео, test, fuzzy, curses, gitter, unicode, twitter, decode, cache, pep, вконтакте, операционная система, ОС, awesome, any, тесты, задача, установка, последовательность, ошибки, генератор, hardcore, toolbar, Wx, магия, doctest, qt, железо, множество, marshal, сигнал, greenlet, слайс, db, future, время, admin, pyside, настройка, regex, module, примесь, slots, sys, email, action, регулярные выражения, одноплатный компьютер, дубликат кода, scrapinghub, micropython, mock, фича, raspberry pi, foreign key, ссылка, pickle, django, нг, модуль, справочник, celery, импорт, лямбда, with, rpc, наука, jinja2, log, обработка текста, super, set, svg, матрица, pygame, fixture, docstring, декларативный язык, fabric, пакет, опыт, магическая переменная, regexp, Гвидо, slice, __all__, gui, база данных, юникод, yattag, cython, матан, документация, анализатор, менеджер контекста, yield, учебник, real-time, слабая ссылка, IPython, браузер, xml, GUI, gc, channel, zip, машинное обучение, библиотека, __future__, ошибка, данные, requirements.txt, оповещение, парсинг, изображение, CLI, tox, фильтр, feedly, R, cffi, http, таблицы, gunicorn, python2, стандарт, if, rss, requests, tkinter, time, mechanize, gensim, интерфейс, Category: Разное, визуализация, postgresql, web parsing, язык, исключение, архитектура, lstrip, parsing, Category: Встречи, путь до файла, copy paste, multiprocessing, объект, rstrip, статический анализатор, lambda