# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals
import six
from collections import OrderedDict
from datetime import datetime, timedelta
from decimal import Decimal
from django.conf import settings
from django.utils.functional import Promise
from django.utils.timezone import get_current_timezone, make_aware
from shuup.apps.provides import get_provide_objects
from shuup.core.models import Shop
from shuup.core.pricing import TaxfulPrice, TaxlessPrice
from shuup.reports.forms import BaseReportForm
from shuup.reports.utils import parse_date_range
from shuup.utils.django_compat import force_text
[docs]class ShuupReportBase(object):
title = ""
description = ""
identifier = ""
filename_template = None
icon = "fa-money"
queryset_row_limit = settings.DEFAULT_REPORTS_ITEM_LIMIT
form_class = BaseReportForm
def __init__(self, **kwargs):
if kwargs.get("initial"):
self.options = kwargs["initial"]
else:
self.options = kwargs
self.start_date = kwargs.get("start_date", None)
self.end_date = kwargs.get("end_date", None)
if self.options.get("date_range"):
self.start_date, self.end_date = parse_date_range(self.options["date_range"])
if self.options.get("shop"):
self.shop = Shop.objects.get(pk=self.options["shop"])
else:
self.shop = None
if self.start_date is None:
self.start_date = make_aware(datetime.min + timedelta(days=1), get_current_timezone())
if self.end_date is None:
self.end_date = make_aware(datetime.max - timedelta(days=1), get_current_timezone())
if self.options.get("request"):
self.request = self.options["request"]
self.rendered = False
def __unicode__(self):
return self.title
@classmethod
[docs] def get_name(cls):
return cls.identifier
@classmethod
[docs] def get_title(cls):
return force_text(cls.title)
@classmethod
[docs] def get_description(cls):
return force_text(cls.description)
@classmethod
[docs] def is_available(cls, request):
try:
from shuup.admin.utils.permissions import has_permission
return has_permission(request.user, cls.identifier)
except ImportError:
return True
[docs] def ensure_texts(self):
"""
Ensure that lazy objects are forced as texts
"""
s = []
for row in self.schema:
if isinstance(row["title"], Promise):
row["title"] = force_text(row["title"])
s.append(row)
self.schema = s
[docs] def get_return_data(self, data, has_totals=True):
return {"start": self.start_date, "end": self.end_date, "data": data, "has_totals": has_totals}
[docs] def dict_getter(self, c, datum):
return datum.get(c["key"])
[docs] def cls_getter(self, c, datum):
return getattr(datum, c["key"], None)
[docs] def read_datum(self, datum):
if isinstance(datum, dict):
getter = self.dict_getter
else:
getter = self.cls_getter
return [(c["getter"] if callable(c.get("getter")) else getter)(c, datum) for c in self.schema]
[docs] def get_totals(self, data):
price_types = [TaxlessPrice, TaxfulPrice]
simple_types = [int, float, Decimal]
countable_types = price_types + simple_types
totals = {}
for datum in data:
for c, val in zip(self.schema, self.read_datum(datum)):
k = c["key"]
if k not in totals:
if type(val) in price_types or type(val) in simple_types:
cls = type(val)
if type(val) in price_types:
totals[k] = cls(0, currency=self.shop.currency)
else:
totals[k] = cls(0)
else:
totals[k] = None
if type(val) in countable_types:
totals[k] = totals[k] + val if totals[k] else val
return totals
[docs]def get_report_class(name, request):
for cls_name, cls in six.iteritems(get_report_classes(request)):
if cls_name == name:
return cls
return None
[docs]def get_report_classes(request=None, provides_key="reports"):
items = {}
for cls in list(get_provide_objects(provides_key)):
if not (request and not cls.is_available(request)):
items[cls.get_name()] = cls
return OrderedDict(sorted(items.items(), key=lambda t: t[1].title))