- PVSM.RU - https://www.pvsm.ru -
В нашем Django-приложении необходимо было разработать отчет (расчет) бонусов.
Отчет должен иметь вложенную структуру с подведением итогов по пользователям, подразделениям и по всей компании. Схематично его логику можно представить:
print total
for department in departments:
print department.total
for user in department.users:
print user.total
for row in user.rows:
print row.data
У этого отчета было два осложняющих момента:
Структура из вложенных словарей отлично решает обе задачи: в них можно сложить все требуемые скаляры (числа, строки, даты), сериализовать и сложить в кэш.
Структура данных для отчета приобрела вид (упрощена):
{
'total': {
'income': 1234,
'bonus': 123,
'expense': 1234,
'penalty': 123
},
'departments': {
'{dept_id}': {
'department': {
'title': 'Mega Department'
}
'total': {
'income': 1234,
'bonus': 123,
'expense': 1234,
'penalty': 123
},
'users': {
'{user_id}': {
'user': {
'name': 'John Smith'
},
'total': {
'income': 1234,
'bonus': 123,
'expense': 1234,
'penalty': 123
},
'rows': {
'{sale_id}': { // Одна модель
'type': 'sale'
'base_income': 1234,
'bonus': 123,
'comment': 'some description'
},
'{expense_id}': { // Другая модель !!!
'type': 'expense'
'expense': 1234,
'penalty': 123,
'comment': 'some description'
},
...
}
},
...
}
},
...
}
}
И вот тут-то я столкнулся с проблемой, что заполнение такой структуры из словарей не столь удобно, как мне того хотелось. Проверка словарей на наличие ключей или использование setdefatult(key, {}) превращает код в нечитабельную кашу.
Эта структура чем-то напоминает XML. И мне бы хотелось использовать что-то подобное тому, как строятся XPath-выражения для адресации узлов XML-дерева:
/departments/{dept_id}/users/{user_id}/rows/{row_id}/base_income
или на языке Python что-то вида:
data.departments.{dept_id}.users.{user_id}.rows.{row_id}.base_income
Учтывая, что {dept_id} и прочие другие {id} — целые числа, то я разрешил себе использование квадратных скобок: [].
data.departments[{dept_id}].users[{user_id}].rows[{row_id}].base_income
Собственно мне нужен был такой класс, который бы вел себя в основном, как словарь, но при этом:
Так появился ElasticDict [1]
Код по подготовке данных выглядит приблизительно так:
data = ElasticDict()
for sale in Sale.objects.filter(...).prefetch_related(...):
data.departments[sale.user.department.pk].users[sale.user.pk].rows[sale.pk] = {'base_income': sale.amount, 'bonus': sale.calc_bonus()}
# или в другой форме, кому как больше нравится
for expense in Expense.objects.filter(...).prefetch_related(...):
data.departments[sale.user.department.pk].users[sale.user.pk].rows[expense.pk].base_expense = expense.amount
data.departments[sale.user.department.pk].users[sale.user.pk].rows[expense.pk].penalty = expense.calc_penalty()
Код в шаблоне так:
{{ data.total }}
{% for dept_id, department in data.departments.items %}
{{ department.total }}
{% for user_id, user in department.users.items %}
{{ user.total }}
{% for row_id, row in user.rows.items %}:
{{ row.data }}
{% endfor %}
{% endfor %}
{% endfor %}
Надо отметить, что ElasticDict() это подкласс обычного dict()'а, т.е. в нем доступно все то, что и в обычном словаре. В тот момент, когда потребуется "зафиксировать" структуру (снова захотим получать KeyError'ы при обращении к несуществующим ключам), экземпляр ElasticDict можно экспортировать в обчный dict(). Делается рекурсивный обход ElasticDict(), где все экземпляры этого класа заменяются на обычные словари. Есть и обратное преобразование — на вход подаем словарь, на выходе получаем ElasticDict также с рекурсивным обходом.
Замечания/предложения приветствуются!
Автор: Ruzin
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/113511
Ссылки в тексте:
[1] ElasticDict: https://github.com/KokocGroup/elastic-dict
[2] Источник: https://habrahabr.ru/post/278075/
Нажмите здесь для печати.