Сервер

Росинтер сервер отчетов: Сервер отчетов и ресурсы для ресторанов

Учебный портал .::. Авторизация пользователя







Вход в корпоративный учебный портал для сотрудников компании / для внешних пользователей

Для авторизации используйте ваш персональный PIN-код для доступа в Личный Кабинет
Компания
1 Самара общий (статистика)АО «Институт стекла»»Росинтер Ресторантс Эккаунт» ЗАОООО «АэроТрейд»ООО «Корпкейтеринг»ООО «Росинтер Ресторантс Санкт-Петербург»ООО «РОСИНТЕР РЕСТОРАНТС УРАЛ»ООО «БРАВА»ООО «ИНКОРОСТ 2004″ООО «Инновации Ресторанного Бизнеса»ООО «Интерпит»ООО «ИП Омск-1″ООО «ИП Омск-2″ООО «ИП Омск-3″ООО «Комбо Компрос»ООО «Комбо Крисанова»ООО «КОМБО Омск-1″ООО «МФ-Менеджмент»ООО «Новые Ресторанные Технологии»ООО «Объединенная Сеть Ресторанов»ООО ПС Омск-1ООО «ПС Тюмень»ООО «Развитие РОСТ»ООО «Ресторанные Технологии Екатеринбург»ООО «Росинтер Ресторантс Екатеринбург»ООО «Росинтер Ресторантс ЗапСиб»ООО «Росинтер Ресторантс Пермь»ООО «СК Омск-1″ООО «СК Омск-3″ООО «СК Тюмень»ООО РентКорп»Артис-Ресторантс» ОООВенита ОООООО «ИП Красный»ООО «ИП Новосибирск-1″ООО «ИП Новосибирск-2″ООО «ИП Новосибирск-3″ООО «ИП Новосибирск-4″ООО «ИП Сургут-1″ООО «Кейтеринг Ресторантс Два»ООО «Кейтеринг Ресторантс Один»ООО «Кейтеринг Ресторантс Три»Кейтеринг Ресторантс ОООООО «КОМБО Барнаул-1″ООО «КОМБО Красноярск-1″ООО «КОМБО Новосибирск-1″Лавсат ОООЛетисса ОООМартин 8 ОООООО «Мульти Профит Медиа»Папильон ОООООО «Пермские Ресторанные Технологии»ПОЧЕТНЫЙ ГОСТЬ ОООООО «ПС Казань — 1″ООО «ПС Казань»ООО «ПС Красноярск-1″ООО «ПС Новосибирск-1″ООО «ПС Новосибирск-2″ООО «ПС Новосибирск-3″ООО «ПС Новосибирск-4″ООО «ПС Сургут-1″ООО «Росинтер Ресторантс Новокузнецк»ООО «Росинтер Ресторантс Татарстан»ООО «РОСИНТЕР РЕСТОРАНТС»РосКорп ОООООО «СК Барнаул-1″ООО «Тисале»ООО «РОСИНТЕР РЕСТОРАНТС СИБИРЬ»ПАО «Росинтер Ресторантс Холдинг»ПАО «КОП «Пулково»
Номер пропуска
*
!
PIN-код
*
!
 

Обзор

О нас

ПАО «Росинтер Ресторантс Холдинг» – лидирующий оператор в сегменте семейных ресторанов (casual dining restaurants) в России и СНГ. Управляет ресторанными сетями под собственными ключевыми брендами «IL Патио» (итальянская кухня), «Шикари» (паназиатская кухня), «Планета Суши» (японская кухня), «Американский Бар и Гриль» (американская кухня), «Мама Раша» (русская кухня), а также развивает международные бренды на основе франчайзинга TGI FRIDAYS (американская кухня) и Costa Coffee (кофейни).

Основные рынки, на которых работает Холдинг – это Россия, СНГ и Центральная Европа, включая страны Балтии. Акции компании котируются на Московской бирже ММВБ-РТС (moex.com) под тикером ROST.

Свой первый ресторан на территории России Компания открыла в 1990 году и заслуженно считается одним из успешных первопроходцев и строителей современной индустрии гостеприимства в России. За более чем двадцатилетний путь бренды и коллектив «Росинтера» завоевали свыше 45 бизнес и общественных наград.

Презентация о компании>>

Стратегия






Стратегические основы

Коллеги

Гости

Портфель

Партнеры

Бизнес

Высоко результативная организация и культура

Бренды и концепции, восхищающие наших гостей

Растущий набор качественных локаций и ресторанов

Прозрачность, ценность, сотрудничество

Устойчивый рост маржи, доходов и денежных потоков

Наши приоритеты

Эффективная и продуктивная организация

 

Развитие передовых практик

 

Вовлечение и коммуникации

 Ревитализация брендов и меню

 

Поддержание инфраструктуры

 

Доставка на дом

 

Фокусировка портфеля

 

Критерии оптимального местоположения

 

Планы развития бренда и географии

 

Переход на качественно иной уровень работы

с франчайзи и инвесторами

 

Прозрачность и справедливость для поставщиков

 

Улучшение системы закупок и снабжения

Рост транзакций и выручки

 

Улучшение показателей работы ресторанов

 

Рост прибыли, устранение потерь

Новости компании



Американская Торговая Палата в России (АТП)


Американская Торговая Палата в России (АТП)


Создана в 1994 году, содействует формированию в России благоприятных условий для предпринимательской деятельности и инвестиций, поддерживая постоянный диалог между западными компаниями и их российскими партнерами.



Российско-Британская Торговая Палата (РБТП)


Российско-Британская Торговая Палата (РБТП)


Основана в 1916 году для укрепления и развития торгово-экономических отношений между Россией и Великобританией.



Федерация Рестораторов и Отельеров (ФРиО)


Федерация Рестораторов и Отельеров (ФРиО)


Cоздана в 1997 году с целью развития индустрии питания и гостеприимства, разработки стандартов и правил предпринимательской и профессиональной деятельности, консолидации предприятий индустрии Гостеприимства.



Российская Ассоциация Франчайзинга


Российская Ассоциация Франчайзинга


Создана в 1997 году для защиты и поддержки интересов своих членов и в целях создания более благоприятной правовой и экономической среды для распространения франчайзинга России



BUYBRAND Club


BUYBRAND Club


Образован в 2006 году, содействует развитию российского франчайзинга и повышению эффективности участия лучших специалистов в сфере франчайзинга во франчайзинговых бизнес-мероприятиях и программах в России.



РусБренд


РусБренд


Крупнейшая российская ассоциация производителей товаров широкого спроса учреждена в 2002 году, деятельность направлена на установление конструктивного диалога со всеми участниками рынка, а также органами государственной власти.

Сервер отчетов на django / Хабр

Доброго времени суток.

Так случилось, что моя работа связана с написанием отчетов.

Этому я посвятил около 8 лет. Отчеты — это глаза бизнес-процесса и информация,

необходимая для принятия оперативных решений.

Вначале наш отдел делал отчеты,

— Принимая задачи по outlook

— Составляя sql-запрос

— Отправляя результаты заказчику в xls

— В лучшем случае, сохраняя sql-код куда-то в папку (а иногда и не сохраняя)

Но это было скучно и неинтересно. Так появилось простейшее приложение на PHP,

в котором каждый отчет был представлен в виде php-файла с одним классом, имеющим единственный (помимо конструктора) метод show()

В таком виде, система прожила 5,5 лет, за которые мной и еще одним человеком было написано более 500 различных отчетов.

В процессе появился опыт и стало понятно, что многое (если не все) сделано не так, да и PHP уже не устраивал.

Сервер отчетов был переписан на django, где была «админка» и код приложения уже не правился.

В процессе работы снова накопилось несколько мыслей,

в итоге сервер был снова переписан.

Я решил представить сообществу текущую версию системы, возможно, она кому-то облегчит жизнь,

сняв рутину отчетности и переложит ее на машину, которая готова сотни раз формировать отчеты

тысячи людям по сотни разных входных параметров.

Это рабочий инструмент, который вы можете (если захотите) использовать в работе,

который нам использовать просто нравится.

Структура проекта

Все заинтересованные, могут сразу ознакомиться с исходным кодом на https://bitbucket.org

Концепция и основные элементы

Система состоит из набора отчетов.

— Отчеты слабо (практически никак) не связаны друг с другом, чтоб разработчик мог быть уверен, что ничего не испортит в других отчетах, отредактировав какой-то один.

— Элементы отчета жестко связанны внутри отчета и влияют друг на друга.

Отчеты не организованы в иерархию (от этого ушли), а помечаются набором тэгов.

На главной системы отчетов можно щелчками набирать нужные сочетания тэгов, которые работают, как фильтр

и поиск отчетов.

Элементы отчета «ленивые» и начинают выполнятся в момент сборки на уровне компоновщика виджетов,

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

размещая в отчете и группированные данные и детализацию.

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

<!-- 
Выполняются только те источники данных,
на которых основаны виджеты, которые должны быть показаны
-->
{% if get.detail == 'table' %}
    {{table.some_table_detail}}
{% elif get.detail == 'chart' %}
    {{charts.some_chart}}
{% else %}    
    {{tables.grouped_table}}
{% endif %}

Есть параметры доступа пользователю к отчету, помимо самого факта доступности.

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

select * from some_table t
where 1 = 1
-- Это инъекция
{% if user_params.only_filials %}and filial_id in ({{user_params.only_filials|join:","}}){% endif %}
-- Это привязанная переменная, которая заменяется на %s 
{% if user_params.only_sectors %}and sector_id = [[user_params.only_sectors.0]]{% endif %}

При необходимости одного варианта вызова менеджера в шаблонной системе, используется __call__

Если возможны несколько вариантов, используется __getitem__ для словарного объекта.

Каждый отчет может состоять из:

Менеджер окружения

class EnvirementManager(object):
    u'''
    Работа с элементами окружения, render строк внутри переменных окружения,
    разбор запросов и т.п.
    '''

    def __init__(self, **kwargs):

        self._env = kwargs

    def render(self, text, **dict):
        u''' Обработка строки шаблонной системой '''

        return render_string(text, self._dict_mix(dict))
    
    def render_template_compiled(self, template, **dict):
        u''' Обработать предварительно скомпилированный шаблон '''
        
        return template.render(Context(self._dict_mix(dict)))
    
    def render_template(self, path, **dict):
        u''' Обработать '''
        
        return render_to_string(path, 
                                self._dict_mix(dict), 
                                context_instance=RequestContext(self._env['request']))
    
    def render_sql(self, sql, **dict):
        u'''  Обрабатывает строку шаблонной системой
        Возвращает обработанную строку и массив переменных для привязки '''

        sql_rendered = self.render(sql, **dict)
        binds = []

        for bind_token in re.findall(r'\[{2}.+\]{2}', sql_rendered):
            env_path = bind_token[2:-2]
            binds.append(attribute_by_name(self._env, env_path))
            sql_rendered = sql_rendered.replace(bind_token, u'%s')

        return (sql_rendered, binds)
    
    def _dict_mix(self, dict):
        
        if dict:
            e = self._env.copy()
            e.update(dict)
        else:
            e = self._env
            
        return e

    def get(self, name, value=None):
        
        return self._env.get(name, value)
    
    def __getitem__(self, name):
        
        return self._env[name]

    def __setitem__(self, name, value):
        
        self._env[name] = value

Инициализируется именованными аргументами и используется для рендеринга всего
с учетом тех переменных, которыми инициализирован.

За счет этого, мы можем гарантировать наличие из любого места заранее известного набора переменных.

Может рендерить sql-запросы, которые являются источниками данных (но об этом чуть ниже)

Менеджер источников данных

class DatasetManager(object):
    u''' Управление источниками данных '''

    def __init__(self, request, dataset, env):
        u'''
        Конструктор
        '''

        self._request = request
        self._dataset = dataset
        self._env = env
        self._cache = []
        self._xml = None

        if self._dataset.xml_settings:
            self._xml = etree.fromstring(self._env.render(self._dataset.xml_settings))

    def get_data(self):
        u'''
        Выполняет запрос и возвращает словарь с данными
        '''

        if not self._cache:

            self._cache = [[], []]
            (sql, binds) = self._env.render_sql(self._dataset.sql)
            cursor = self._modify_cursor(connections['reports'].cursor())
            cursor.execute(sql, binds)

            # Настройки колонок запроса
            xml_columns = {}
            if self._xml not in (None, ''):
                for xml_column in self._xml.xpath('/xml/columns/column'):
                    attrs = xml_column.attrib
                    xml_columns[force_unicode(attrs['name'])] = force_unicode(attrs['alias'])

            # Колонки запроса (заменяем название колонки, если запрошено)
            # это может пригодится при использовании БД, запрещаюей использование длинных имен
            for field in cursor.description:
                name_unicode = force_unicode(field.name)
                self._cache[0].append(xml_columns[name_unicode] if name_unicode in xml_columns
                                                                else name_unicode)

            self._cache[1] = cursor.fetchall()

        return self._cache

    def __getitem__(self, name):
        u''' вызовы из шаблонной системы работают через словарные объекты '''

        if name == 'sql':
            return self._env.render(self._dataset.sql)

        elif name == 'render':
            (sql, binds) = self._env.render_sql(self._dataset.sql)
            return {'sql': sql, 'binds': binds}

        elif name == 'data':
            (fields, data) = self.get_data()
            return [dict(zip(fields, row)) for row in data]

    def _modify_cursor(self, cursor):
        u'''
        Модификация параметров курсора (может потредоваться для разных баз баз данных),
        например, для Oracle требуется
        установка параметров локали, отключение number_as_string = True (по умолчанию в стандартном
        backends django)
        '''

        return cursor

Поставляет:
— Данные в виде кортежа (field_names, rows,)
Для визуализации различными типами виджетов, выгрузки в excel
— Данные в виде списка словарей (каждая строка является словарем)
Для прямого обращения к источнику и генерации html, javascript, css кода в менеджере компоновки (о нем немного позже)
— sql-код без обработки переменных привязки
Используется для организации вложенных источников данных, например:

  select key, count(1) from ({{datasets.base_dataset.sql}}) group by key
  

— sql-код и переменные привязки
Просто для отладки и выводе в отчет

За счет этого, можно:

— Менять на лету sql-запрос в зависимости от переменных менеджера окружения

— На основе одного запроса, можно делать другие так, чтоб у вас группировка и детализация никогда не будут отличатся,

так как основаны на одном источнике, и вы никогда не забудите доработать группировку и забыть сделать это в детализации

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

Например, создать заранее неизвестное кол-во колонок в запросе помесячно, по месяцам (или неделям), входящим в отчетный период (задается пользователем в форме, но о них тоже чуть ниже),

и все это независимо от возможностей базы данных (без pivot|unpivot oracle и xml_query oracle)

Менеджер фильтров

class FilterManager(object):
    u''' Класс для управления фильтрами '''
    
    def __init__(self, request, filter_obj, env):
        u''' Конструктор '''
        
        self._request = request
        self._filter_obj = filter_obj
        self._env = env
        self._form_instance = None
        self._changed_data = {}
        
        # Если отправили эту форму, инициализировать форму POST-данными
        # Проверить отправленные данные и, если они отличаются от сохраненных, запомним их в self._changed_data
        if self._request.method == 'POST' and int(self._request.POST['filter_id']) == self._filter_obj.id:
            self._form_instance = self._get_form_class()(self._request.POST)
            if self._form_instance.is_valid():
                data = self._form_instance.cleaned_data
                for key, value in data.items():
                    if self._env['f'].get(key) != value:
                        self._changed_data[key] = value
        
    def get_changed_data(self):
        
        return self._changed_data
    
    def get_form(self):
        u''' Получить экземпляр формы фильтра '''
        
        try:

            # Если экземпляр формы не создан в конструкторе, (при изменениях)
            # значит инициализируем из заранее сохраненных значений
            if self._form_instance is None:
                self._form_instance = self._get_form_class()(initial=self._env['f'])
                
            html = self._env.render('{% load custom_filters %}' + self._filter_obj.html_layout, form=self._form_instance)
            return self._env.render_template('reports_filter.html', form_html=html, filter=self._filter_obj)

        except Exception as e:
            return e
    
    def __call__(self):
        u''' Для вызова в шаблоне '''
        try:
            return self.get_form()
        except Exception as e:
            return e
    
    def _get_form_class(self):
        u''' Собрать форму фильтра и вернуть ее '''
        
        form_attrs = {}
        
        filter_widgets = (DateField, ChoiceField, MultipleChoiceField, BooleanField, CharField, CharField, DateRangeField)
    
        for item in self._filter_obj.form_items.all():
    
            kwargs = {'label': item.title, 'required': False}
            if item.xml_settings:
                xml = xml = etree.fromstring(self._env.render(item.xml_settings))
            else:
                xml = None
    
            if item.widget_type == 0:
                kwargs['widget'] = forms.DateInput(attrs={'class': 'date'})
    
            elif item.widget_type in (1, 2):
                choices = []
                for option in xml.xpath('/xml/options/option'):
                    choices.append((option.attrib['id'], option.attrib['value']))
                sql = xml.xpath('/xml/sql')
                if sql:
                    curs = connections['reports'].cursor().execute(sql[0].text)
                    for row in curs.fetchall():
                        choices.append((row[0], row[1]))
                kwargs['choices'] = choices
    
            elif item.widget_type == 4:
                kwargs['max_length'] = 50
    
            elif item.widget_type == 5:
                default = xml.xpath('/xml/value')
                kwargs['widget'] = forms.HiddenInput(attrs={'value': default[0].text})
    
            form_attrs[item.key] = filter_widgets[item.widget_type](**kwargs)
    
        filter_form = type(str(self._filter_obj.title), (forms.Form,), form_attrs)

        return filter_form

Тут все достаточно тривиально. Собираем форму и отдаем форму и измененные данные

Табличный виджет

class WidgetTableManager(object):
    u''' Управляет работой с табличным виджетом '''

    def __init__(self, request, widget_obj, dataset, env):
        u''' Конструктор '''

        self._request = request
        self._widget_obj = widget_obj
        self._dataset = dataset
        self._env = env
        self._xml = None

        if widget_obj.xml_settings:
            self._xml = etree.fromstring(self._env.render(widget_obj.xml_settings).replace('[[', '{{').replace(']]', '}}'))

    def get_html(self):
        u''' Вернуть html-код таблицы '''

        (fields, data) = self._dataset.get_data()

        field_settings = {}
        table_settings = {}
        if self._xml is not None:

            table_settings_node = xml_node(self._xml, '/xml/table')
            if table_settings_node is not None:
                table_settings = table_settings_node.attrib
            for xml in self._xml.xpath('/xml/fields/field'):
                xml_attributes = dict(xml.attrib)
                field_name = xml_attributes['name']

                if 'lnk' in xml_attributes:
                    xml_attributes['tpl_lnk'] = Template(force_unicode(xml_attributes['lnk']))
                if 'cell_attributes' in xml_attributes:
                    xml_attributes['tpl_cell_attributes'] = Template(force_unicode(xml_attributes['cell_attributes']))

                field_settings[field_name] = xml_attributes

        # Выводимые на экран колонки
        fields_visible = []
        for index, field_name in enumerate(fields):
            settings = field_settings.get(field_name, {})
            if 'display' in settings and settings['display'] == '0':
                continue
            fields_visible.append((index, field_name, settings))

        # Вычислить параметры и привязать к данным
        rows = []
        for row in data:
            row_dict = dict(zip(fields, row))
            row_settings = {}

            # Настройки уровня строки
            if 'field_row_style' in table_settings:
                row_settings['row_style'] = row_dict[table_settings['field_row_style']]
            if 'field_row_attributes' in table_settings:
                row_settings['row_attributes'] = row_dict[table_settings['field_row_attributes']]

            # Перебрать строки и собрать настройки уровня строки
            fields_set = []
            for index, field_name, settings in fields_visible:
                field = {'name': field_name, 'value': row[index]}

                # Если есть ссылка и она должна быть показана
                if 'tpl_lnk' in settings and ('lnk_enable_field' not in settings or row_dict[settings['lnk_enable_field']] not in (0, '0', '', None)):
                    field['lnk'] = self._env.render_template_compiled(settings['tpl_lnk'], row=row_dict)

                # Аттрибуты ячейки
                if 'tpl_cell_attributes' in settings:
                    field['cell_attributes'] = settings['tpl_cell_attributes'].render(Context(row_dict))

                field['settings'] = settings
                fields_set.append(field)

            rows.append({'settings': row_settings, 'fields': fields_set})

        return render_to_string('reports_widget_table.html', {'fields': fields_visible, 'rows': rows, 'widget_obj': self._widget_obj})

    def __call__(self):
        u''' При вызове, возвращает таблицу '''

        try:
            return self.get_html()
        except Exception as e:
            return u'Ошибка в виджете %s: "%s"' % (self._widget_obj.title, e)

Берет данные из источника данных и выводит в табличном виде. Поддерживает

— форматирование

— Генерацию ссылок (которые могут использоваться для детализации ячейки в другой таблице, график или excel)

— Генерацию произвольных аттрибутов строк (для работы javascript, скрытия или показа итоговых строк и т.п.)

Виджет — график

class WidgetChartManager(object):
    u''' Менеджер графика '''

    def __init__(self, request, chart_obj, dataset, env):
        u''' Конструктор '''

        self._request = request
        self._chart_obj = chart_obj
        self._dataset = dataset
        self._env = env
        self._xml = etree.fromstring(self._env.render(self._chart_obj.xml_settings))

        print 1

    def __call__(self):
        u''' При вызове из шаблона '''

        print 2
        try:
            return self.get_chart()
        except Exception as e:
            print unicode(e)
            return unicode(e)

    def get_chart(self):
        u''' html со скриптом сборки графика '''

        (fields, data) = self._dataset.get_data()
        return self._env.render_template('reports_widget_chart.html',
                                         settings=xml_to_dict(xml_node(self._xml, '/xml')),
                                         data=json.dumps([dict(zip(fields, row)) for row in data], cls=JSONEncoder),
                                         chart_obj=self._chart_obj,
                                         )

Тут тоже просто. Берет данные из источника, XML-настройки переводит в dict и рендерит шаблон,

собирая javascript-код графика (используется amcharts)

тэги XML-узлов преобразуются в название парамерта, текст в значение параметра,

то есть можно использовать практически все параметры библиотеки amcharts,

просто поместив нужный тэг у нужную секцию

И, как завершение теоретической части, привожу код класса, который всем этим управляет,

размещая виджеты, или возвращая xls или произвольный документ (html с расширением .doc или .xls)

Табличный виджет

class WidgetTableManager(object):
    u''' Управляет работой с табличным виджетом '''

    def __init__(self, request, widget_obj, dataset, env):
        u''' Конструктор '''

        self._request = request
        self._widget_obj = widget_obj
        self._dataset = dataset
        self._env = env
        self._xml = None

        if widget_obj.xml_settings:
            self._xml = etree.fromstring(self._env.render(widget_obj.xml_settings).replace('[[', '{{').replace(']]', '}}'))

    def get_html(self):
        u''' Вернуть html-код таблицы '''

        (fields, data) = self._dataset.get_data()

        field_settings = {}
        table_settings = {}
        if self._xml is not None:

            table_settings_node = xml_node(self._xml, '/xml/table')
            if table_settings_node is not None:
                table_settings = table_settings_node.attrib
            for xml in self._xml.xpath('/xml/fields/field'):
                xml_attributes = dict(xml.attrib)
                field_name = xml_attributes['name']

                if 'lnk' in xml_attributes:
                    xml_attributes['tpl_lnk'] = Template(force_unicode(xml_attributes['lnk']))
                if 'cell_attributes' in xml_attributes:
                    xml_attributes['tpl_cell_attributes'] = Template(force_unicode(xml_attributes['cell_attributes']))

                field_settings[field_name] = xml_attributes

        # Выводимые на экран колонки
        fields_visible = []
        for index, field_name in enumerate(fields):
            settings = field_settings.get(field_name, {})
            if 'display' in settings and settings['display'] == '0':
                continue
            fields_visible.append((index, field_name, settings))

        # Вычислить параметры и привязать к данным
        rows = []
        for row in data:
            row_dict = dict(zip(fields, row))
            row_settings = {}

            # Настройки уровня строки
            if 'field_row_style' in table_settings:
                row_settings['row_style'] = row_dict[table_settings['field_row_style']]
            if 'field_row_attributes' in table_settings:
                row_settings['row_attributes'] = row_dict[table_settings['field_row_attributes']]

            # Перебрать строки и собрать настройки уровня строки
            fields_set = []
            for index, field_name, settings in fields_visible:
                field = {'name': field_name, 'value': row[index]}

                # Если есть ссылка и она должна быть показана
                if 'tpl_lnk' in settings and ('lnk_enable_field' not in settings or row_dict[settings['lnk_enable_field']] not in (0, '0', '', None)):
                    field['lnk'] = self._env.render_template_compiled(settings['tpl_lnk'], row=row_dict)

                # Аттрибуты ячейки
                if 'tpl_cell_attributes' in settings:
                    field['cell_attributes'] = settings['tpl_cell_attributes'].render(Context(row_dict))

                field['settings'] = settings
                fields_set.append(field)

            rows.append({'settings': row_settings, 'fields': fields_set})

        return render_to_string('reports_widget_table.html', {'fields': fields_visible, 'rows': rows, 'widget_obj': self._widget_obj})

    def __call__(self):
        u''' При вызове, возвращает таблицу '''

        try:
            return self.get_html()
        except Exception as e:
            return u'Ошибка в виджете %s: "%s"' % (self._widget_obj.title, e)

Берет данные из источника данных и выводит в табличном виде. Поддерживает

— форматирование

— Генерацию ссылок (которые могут использоваться для детализации ячейки в другой таблице, график или excel)

— Генерацию произвольных аттрибутов строк (для работы javascript, скрытия или показа итоговых строк и т.п.)

Менеджер отчетов

class ReportManager(object):
    u''' Управляет отчетами '''

    def __init__(self, request, report):

        self._report = report
        self._request = request
        self._user = request.user
        self._forms = {}
        self._env = EnvirementManager(request=request, user_params=self._get_user_params(), forms={}, f={})
        self._datasets = {}
        self._widgets_table = {}
        self._widgets_chart = {}

        self._load_stored_filter_values()

        # Сборка фильтров
        for filter_obj in self._report.forms.only('title', 'html_layout'):
            filter_manager = FilterManager(self._request, filter_obj, self._env)
            self._save_stored_filter_values(filter_manager.get_changed_data())
            self._forms[filter_obj.title] = filter_manager
        self._env['forms'] = self._forms

        # Собираем источники данных
        for ds in self._report.datasets.only('sql', 'title', 'xml_settings'):
            self._datasets[ds.title] = DatasetManager(request, ds, self._env)
        self._env['datasets'] = self._datasets

        # Собираем виджет-таблицы
        for widget_obj in self._report.widgets_table.only('title', 'dataset', 'table_header', 'xml_settings'):
            self._widgets_table[widget_obj.title] = WidgetTableManager(self._request,
                                                                       widget_obj,
                                                                       self._datasets[widget_obj.dataset.title],
                                                                       self._env)
        self._env['tables'] = self._widgets_table

        # Виджеты - графики
        for chart_obj in self._report.widgets_chart.only('title', 'dataset', 'xml_settings'):
            self._widgets_chart[chart_obj.title] = WidgetChartManager(self._request,
                                                                      chart_obj,
                                                                      self._datasets[chart_obj.dataset.title],
                                                                      self._env)
        self._env['charts'] = self._widgets_chart

    def get_request(self):
        u''' Результат отчета '''

        response_type = self._request.REQUEST.get('response_type', 'html')

        if response_type == 'xls':
            return self._get_request_xls(self._request.REQUEST['xls'])
        elif response_type == 'template':
            return self._get_request_template(self._request.REQUEST['template'])
        else:
            return self._get_request_html()

    def _get_request_html(self):
        u''' Вернуть результат в виде html для вывода на экран '''

        context = {'favorite_reports': self._user.reports_favorited.all()}
        context['report_body'] = self._env.render(self._report.html_layout)
        context['breadcrumbs'] = (('Отчеты', reverse('reports_home')), (self._report.title, None))
        context['filter_presets'] = self._report.filter_presets.filter(user=self._user)
        context['report'] = self._report

        return render(self._request, 'reports_report.html', context)

    def _get_request_template(self):
        u''' Вернуть результат в виде html для вывода на экран '''

        # TODO
        raise NotImplementedError(u'Не реализовано')

    def _get_request_xls(self, dataset_title):
        u""" Вернуть результат выгрузки в xls """

        dataset = self._datasets[dataset_title]
        (columns, data) = dataset.get_data()
        w = Workbook(optimized_write=True)
        sheet = w.create_sheet(0)
        sheet.append(columns)

        rows_in_sheet = 0
        for row in data:

            if rows_in_sheet > 1000000:
                sheet = w.create_sheet()
                sheet.append(columns)
                rows_in_sheet = 0

            sheet.append(row)
            rows_in_sheet += 1

        try:
            tmpFileName = os.tempnam()
            w.save(tmpFileName)
            fh = open(tmpFileName, 'rb')
            resp = HttpResponse(fh.read(), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
        finally:
            if fh:
                fh.close()
            os.unlink(tmpFileName)

        resp['Content-Disposition'] = 'attachment; filename="Выгрузка.xlsx"'
        return resp

    def _get_request_document_template(self, template_title):
        u""" Вернуть ответ, сгенерировав документ по шаблону """

        pass

    def _save_stored_filter_values(self, values):
        u"""
        Записать значения фильтров в базу и в _env
        Если результат не html, то в базу ничего не сохраняем,
        потому что не происходит перезагрузки страницы и не нужно восстанавливать значения фильтров
        """

        for key, value in values.items():
            self._env['f'][key] = value
            if values.get('response_type', 'html') == 'html':
                (store_item, is_created) = models.WidgetFormStorage.objects.get_or_create(user=self._user, report=self._report, key=key)
                store_item.value = pickle.dumps(value)
                store_item.save()

    def _load_stored_filter_values(self):
        u""" Загрузить значения форм, считав их из базы данных """

        for item in self._report.form_storage.all():
            self._env['f'][item.key] = pickle.loads(item.value)

    def _get_user_params(self):
        u""" Вернуть параметры пользователя """

        params = {}

        try:

            param_string = models.UserAccess.objects.get(report=self._report, user=self._user).params
            if param_string:
                for pair in param_string.split(';'):
                    (key, values_str) = pair.split('=')
                    values = values_str.split(',')
                    params[key] = values

        except Exception, e:
            pass

        return params

Имеет только один публичный метод, который возвращает httpResponse (html, вложение или xlsx)

Вот, в общем-то и все.

Интерфейс администратора описывать не стал.

css от bootstrap

Немного практики и картинок

Источник данных ds

select key, value, value * 0.5 as value1 from (
    select 1 as key, 2000 as value union all
    select 2 as key, 4000 as value union all
    select 3 as key, 6000 as value union all
    select 4 as key, 3000 as value union all
    select 5 as key, 2000 as value union all
    select 6 as key, 1000 as value
) t
{% if f.doble_rows %}cross join (select 1 union all select 2) t1 {% endif %}

Источник данных ds1

select 
t.key as key,
t.value as value,
case when key = 1 then 'background-color: #dff0d8;' end as row_style_field,
case when key = 2 then 'class="error"' end as row_attribute_field
{% if f.add_column %}
    {% for row in datasets.ds.data %}
    , {{row.key}} as Поле{{row.key}} 
    {% endfor %}
{% endif %}
from ({{datasets.ds.sql}}) t
    {% if request.GET.key %} where key = [[request.GET.key]] {% endif %}
    {% if f.limit %} limit [[f.limit]] {% endif %}

Табличный виджет t:

Создадим, выбрав из списка источник ds1

и прописав название. Все остальное не трогаем.

Таблица t1 (на ds1)

<xml>
    <table field_row_style='row_style_field' field_row_attributes='row_attribute_field'/>
    <fields>
        <field name='Ключ' classes='text-right'/>
        <field name='value' classes='text-right' lnk='?response_type=xls&xls=ds&key=[[row.Ключ]]&from=value'/>
        {% for row in datasets.ds.data %}
        <field name='Поле{{row.key}}' classes='text-right' lnk='?response_type=xls&xls=ds1&key=[[row.Ключ]]&from=Поле{{row.key}}'/>
        {% endfor %}
        <field name='Поле1' display='0'/>
        <field name='row_style_field' display='0'/>
        <field name='row_attribute_field' display='0'/>
    </fields>
</xml>

Таблица t1 (на ds1)

<xml>
    <table field_row_style='row_style_field' field_row_attributes='row_attribute_field'/>
    <fields>
        <field name='Ключ' classes='text-right'/>
        <field name='value' classes='text-right' lnk='?response_type=xls&xls=ds&key=[[row.Ключ]]&from=value'/>
        {% for row in datasets.ds.data %}
        <field name='Поле{{row.key}}' classes='text-right' lnk='?response_type=xls&xls=ds1&key=[[row.Ключ]]&from=Поле{{row.key}}'/>
        {% endfor %}
        <field name='Поле1' display='0'/>
        <field name='row_style_field' display='0'/>
        <field name='row_attribute_field' display='0'/>
    </fields>
</xml>

График test (на ds)

<xml>

    <chart>
        <categoryField>key</categoryField>
        <marginTop>32</marginTop>
    </chart>

    <categoryAxis>
        <labelsEnabled>true</labelsEnabled>
        <gridCount>50</gridCount>
        <equalSpacing>true</equalSpacing>
    </categoryAxis>

    <valueAxis>
        <valueAxisLeft>
            <stackType>regular</stackType> 
            <gridAlpha>0.07</gridAlpha>
        </valueAxisLeft>
    </valueAxis>

    <cursor>
        <bulletsEnabled>true</bulletsEnabled>
    </cursor>

    <graphs>
        <graph>
            <type>column</type>
            <title>Отказы</title>
            <valueField>value</valueField>
            <balloonText>[[category]] дней: [[value]] шт.</balloonText>
            <lineAlpha>0</lineAlpha>
            <fillAlphas>0.6</fillAlphas>
        </graph>
        <graph2>
            <type>column</type>
            <title>Не отказы</title>
            <valueField>value1</valueField>
            <balloonText>[[category]] дней: [[value]] шт.</balloonText>
            <lineAlpha>0</lineAlpha>
            <fillAlphas>0.6</fillAlphas>
        </graph2>
    </graphs>

</xml>

График test1 (на ds)

<xml>

    <chart>
        <categoryField>key</categoryField>
        <marginTop>32</marginTop>
    </chart>

    <categoryAxis>
        <labelsEnabled>true</labelsEnabled>
        <gridCount>50</gridCount>
        <equalSpacing>true</equalSpacing>
    </categoryAxis>

    <valueAxis>
        <valueAxisLeft>
            <gridAlpha>0.07</gridAlpha>
        </valueAxisLeft>
    </valueAxis>

    <cursor>
        <bulletsEnabled>true</bulletsEnabled>
    </cursor>

    <graphs>
        <graph>
            <type>column</type>
            <title>Отказы</title>
            <valueField>value</valueField>
            <balloonText>[[category]] дней: [[value]] шт.</balloonText>
            <lineAlpha>0</lineAlpha>
            <fillAlphas>0.6</fillAlphas>
        </graph>
        <graph2>
            <type>column</type>
            <title>Не отказы</title>
            <valueField>value1</valueField>
            <balloonText>[[category]] дней: [[value]] шт.</balloonText>
            <lineAlpha>0</lineAlpha>
            <fillAlphas>0.6</fillAlphas>
        </graph2>
        <graph3>
            <type>smoothedLine</type>
            <title>Не отказы</title>
            <valueField>value1</valueField>
        </graph3>
    </graphs>

</xml>

Размещаем на экране

<h3>Работа с источниками данных</h3>
<div>{{forms.f}}</div>

<h4>После пре-процессора django template</h4>
<pre>
sql:{{datasets.ds1.render.sql}}
параметры:{{datasets.ds1.render.binds}}
</pre>

<h4>После выполнения</h4>
{{tables.t}}
<a href='?response_type=xls&xls=ds1'>В Excel</a>

<h3>Визуализация данных</h3>
{{tables.t1}}
<div>{{charts.test}}</div>
<div>{{charts.test1}}</div>

Админка выглядит так
Вот что получилосьСписок отчетов:

Наш отчет:

Хочу сказать спасибо за помощь Павлу, Александру, Евгению.

Спасибо за внимание. Если хотите, можете взять это из репозитория и использовать по своему усмотрению.
bitbucket.org/dibrovsd/py_docflow

P.S. в репозитории есть еще практически законченное приложение документооборота,

но о нем немного позже, если вам это будет интересно.

P.P.S.

Наверняка найдется множество неоптимальных решений, с python и django я знаком недавно.

Все конструктивные предложения прошу писать в «личку».

ReportServer — документация

Инструкции по обновлению
1 Обновите ReportServer до 3.3.0

Инструкции по обновлению для обновления до ReportServer 3.3.0 как для установки Bitnami, так и для установки вручную.

2 Обновите ReportServer 2.2 до 3.0.1

Инструкции по обновлению для обновления ReportServer 2.2 в ReportServer 3.0.1 для ручной установки.

3 Обновите стек Bitnami ReportServer

Инструкции по обновлению, чтобы полностью обновить стек ReportServer Bitnami.

Установка
1 Лучшая практика — установка ReportServer вручную в Ubuntu

В этом практическом руководстве мы подробно описываем, как настроить ReportServer в системе Ubuntu.Принимая Ubuntu в качестве базовой операционной системы, это руководство должно быть легко
адаптируется к выбранной вами операционной системе.

2 Лучшая практика — установка ReportServer вручную в Windows

В этом практическом руководстве мы подробно описываем, как настроить ReportServer в Windows 10 с MariaDB в качестве базовой базы данных.

Начало работы
1 Начиная

В руководстве представлены демонстрационные данные (и онлайн-демонстрационная система) и демонстрационные примеры.
базовый функционал ReportServer.

2 Начало работы с разрешениями

Это руководство дает краткое введение в работу с разрешениями ReportServer.

3 Настройка мультитенантной системы

В этом руководстве показано, как использовать систему разрешений ReportServer и пользовательские переменные для настройки мультитенантных систем.

Настройка
1 Перевод ReportServer — I18N

В руководстве показано, как можно расширить перевод ReportServer.

2 Тематика — адаптация пользовательского интерфейса ReportServer

В руководстве показано, как создать настраиваемую тему для ReportServer Eneterprise Edition.

Аутентификация
1 Введение в настраиваемые аутентификаторы

В этом руководстве представлена ​​подключаемая система аутентификации ReportServer и показано, как пользовательские аутентификаторы могут быть добавлены в ReportServer Enterprise.

Создание сценариев
1 Начало работы со сценариями ReportServer

Введение в мир сценариев ReportServer.

Развитие
1 Настройка сообщества ReportServer в Eclipse

В руководстве объясняется, как настроить ReportServer Community Edition в Eclipse.

.Службы отчетов

— перечисление всех источников данных и их зависимостей (отчеты, элементы и т. Д.) В SQL Server 2008 R2

Переполнение стека

  1. Около
  2. Продукты

  3. Для команд
  1. Переполнение стека
    Общественные вопросы и ответы

  2. Переполнение стека для команд
    Где разработчики и технологи делятся частными знаниями с коллегами

  3. Вакансии
    Программирование и связанные с ним технические возможности карьерного роста

  4. Талант
    Нанимайте технических специалистов и создавайте свой бренд работодателя

  5. Реклама
    Обратитесь к разработчикам и технологам со всего мира

  6. О компании

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *