from datetime import timedelta
from decimal import Decimal as D
from decimal import ROUND_UP
from django.db.models import Avg, Count, Sum
from django.utils.timezone import now
from django.views.generic import TemplateView
from oscar.apps.promotions.models import AbstractPromotion
from oscar.core.compat import get_user_model
from oscar.core.loading import get_model
ConditionalOffer = get_model('offer', 'ConditionalOffer')
Voucher = get_model('voucher', 'Voucher')
Basket = get_model('basket', 'Basket')
StockAlert = get_model('partner', 'StockAlert')
Product = get_model('catalogue', 'Product')
Order = get_model('order', 'Order')
Line = get_model('order', 'Line')
User = get_user_model()
[docs]class IndexView(TemplateView):
"""
An overview view which displays several reports about the shop.
Supports the permission-based dashboard. It is recommended to add a
index_nonstaff.html template because Oscar's default template will
display potentially sensitive store information.
"""
def get_template_names(self):
if self.request.user.is_staff:
return ['dashboard/index.html', ]
else:
return ['dashboard/index_nonstaff.html', 'dashboard/index.html']
def get_context_data(self, **kwargs):
ctx = super(IndexView, self).get_context_data(**kwargs)
ctx.update(self.get_stats())
return ctx
[docs] def get_active_site_offers(self):
"""
Return active conditional offers of type "site offer". The returned
``Queryset`` of site offers is filtered by end date greater then
the current date.
"""
return ConditionalOffer.objects.filter(
end_datetime__gt=now(), offer_type=ConditionalOffer.SITE)
[docs] def get_active_vouchers(self):
"""
Get all active vouchers. The returned ``Queryset`` of vouchers
is filtered by end date greater then the current date.
"""
return Voucher.objects.filter(end_datetime__gt=now())
[docs] def get_open_baskets(self, filters=None):
"""
Get all open baskets. If *filters* dictionary is provided they will
be applied on all open baskets and return only filtered results.
"""
if filters is None:
filters = {}
filters['status'] = Basket.OPEN
return Basket.objects.filter(**filters)
[docs] def get_hourly_report(self, hours=24, segments=10):
"""
Get report of order revenue split up in hourly chunks. A report is
generated for the last *hours* (default=24) from the current time.
The report provides ``max_revenue`` of the hourly order revenue sum,
``y-range`` as the labeling for the y-axis in a template and
``order_total_hourly``, a list of properties for hourly chunks.
*segments* defines the number of labeling segments used for the y-axis
when generating the y-axis labels (default=10).
"""
# Get datetime for 24 hours agao
time_now = now().replace(minute=0, second=0)
start_time = time_now - timedelta(hours=hours - 1)
orders_last_day = Order.objects.filter(date_placed__gt=start_time)
order_total_hourly = []
for hour in range(0, hours, 2):
end_time = start_time + timedelta(hours=2)
hourly_orders = orders_last_day.filter(date_placed__gt=start_time,
date_placed__lt=end_time)
total = hourly_orders.aggregate(
Sum('total_incl_tax')
)['total_incl_tax__sum'] or D('0.0')
order_total_hourly.append({
'end_time': end_time,
'total_incl_tax': total
})
start_time = end_time
max_value = max([x['total_incl_tax'] for x in order_total_hourly])
divisor = 1
while divisor < max_value / 50:
divisor *= 10
max_value = (max_value / divisor).quantize(D('1'), rounding=ROUND_UP)
max_value *= divisor
if max_value:
segment_size = (max_value) / D('100.0')
for item in order_total_hourly:
item['percentage'] = int(item['total_incl_tax'] / segment_size)
y_range = []
y_axis_steps = max_value / D(str(segments))
for idx in reversed(range(segments + 1)):
y_range.append(idx * y_axis_steps)
else:
y_range = []
for item in order_total_hourly:
item['percentage'] = 0
ctx = {
'order_total_hourly': order_total_hourly,
'max_revenue': max_value,
'y_range': y_range,
}
return ctx
def get_stats(self):
datetime_24hrs_ago = now() - timedelta(hours=24)
orders = Order.objects.all()
orders_last_day = orders.filter(date_placed__gt=datetime_24hrs_ago)
open_alerts = StockAlert.objects.filter(status=StockAlert.OPEN)
closed_alerts = StockAlert.objects.filter(status=StockAlert.CLOSED)
total_lines_last_day = Line.objects.filter(
order__in=orders_last_day).count()
stats = {
'total_orders_last_day': orders_last_day.count(),
'total_lines_last_day': total_lines_last_day,
'average_order_costs': orders_last_day.aggregate(
Avg('total_incl_tax')
)['total_incl_tax__avg'] or D('0.00'),
'total_revenue_last_day': orders_last_day.aggregate(
Sum('total_incl_tax')
)['total_incl_tax__sum'] or D('0.00'),
'hourly_report_dict': self.get_hourly_report(hours=24),
'total_customers_last_day': User.objects.filter(
date_joined__gt=datetime_24hrs_ago,
).count(),
'total_open_baskets_last_day': self.get_open_baskets({
'date_created__gt': datetime_24hrs_ago
}).count(),
'total_products': Product.objects.count(),
'total_open_stock_alerts': open_alerts.count(),
'total_closed_stock_alerts': closed_alerts.count(),
'total_site_offers': self.get_active_site_offers().count(),
'total_vouchers': self.get_active_vouchers().count(),
'total_promotions': self.get_number_of_promotions(),
'total_customers': User.objects.count(),
'total_open_baskets': self.get_open_baskets().count(),
'total_orders': orders.count(),
'total_lines': Line.objects.count(),
'total_revenue': orders.aggregate(
Sum('total_incl_tax')
)['total_incl_tax__sum'] or D('0.00'),
'order_status_breakdown': orders.order_by(
'status'
).values('status').annotate(freq=Count('id'))
}
return stats