Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

views.py 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. from datetime import timedelta
  2. from decimal import Decimal as D, ROUND_UP
  3. from django.utils.timezone import now
  4. from django.views.generic import TemplateView
  5. from oscar.core.loading import get_model
  6. from django.db.models import Avg, Sum, Count
  7. from oscar.core.compat import get_user_model
  8. from oscar.apps.promotions.models import AbstractPromotion
  9. ConditionalOffer = get_model('offer', 'ConditionalOffer')
  10. Voucher = get_model('voucher', 'Voucher')
  11. Basket = get_model('basket', 'Basket')
  12. StockAlert = get_model('partner', 'StockAlert')
  13. Product = get_model('catalogue', 'Product')
  14. Order = get_model('order', 'Order')
  15. Line = get_model('order', 'Line')
  16. User = get_user_model()
  17. class IndexView(TemplateView):
  18. """
  19. An overview view which displays several reports about the shop.
  20. Supports the permission-based dashboard. It is recommended to add a
  21. index_nonstaff.html template because Oscar's default template will
  22. display potentially sensitive store information.
  23. """
  24. def get_template_names(self):
  25. if self.request.user.is_staff:
  26. return ['dashboard/index.html', ]
  27. else:
  28. return ['dashboard/index_nonstaff.html', 'dashboard/index.html']
  29. def get_context_data(self, **kwargs):
  30. ctx = super(IndexView, self).get_context_data(**kwargs)
  31. ctx.update(self.get_stats())
  32. return ctx
  33. def get_active_site_offers(self):
  34. """
  35. Return active conditional offers of type "site offer". The returned
  36. ``Queryset`` of site offers is filtered by end date greater then
  37. the current date.
  38. """
  39. return ConditionalOffer.objects.filter(
  40. end_datetime__gt=now(), offer_type=ConditionalOffer.SITE)
  41. def get_active_vouchers(self):
  42. """
  43. Get all active vouchers. The returned ``Queryset`` of vouchers
  44. is filtered by end date greater then the current date.
  45. """
  46. return Voucher.objects.filter(end_datetime__gt=now())
  47. def get_number_of_promotions(self, abstract_base=AbstractPromotion):
  48. """
  49. Get the number of promotions for all promotions derived from
  50. *abstract_base*. All subclasses of *abstract_base* are queried
  51. and if another abstract base class is found this method is executed
  52. recursively.
  53. """
  54. total = 0
  55. for cls in abstract_base.__subclasses__():
  56. if cls._meta.abstract:
  57. total += self.get_number_of_promotions(cls)
  58. else:
  59. total += cls.objects.count()
  60. return total
  61. def get_open_baskets(self, filters=None):
  62. """
  63. Get all open baskets. If *filters* dictionary is provided they will
  64. be applied on all open baskets and return only filtered results.
  65. """
  66. if filters is None:
  67. filters = {}
  68. filters['status'] = Basket.OPEN
  69. return Basket.objects.filter(**filters)
  70. def get_hourly_report(self, hours=24, segments=10):
  71. """
  72. Get report of order revenue split up in hourly chunks. A report is
  73. generated for the last *hours* (default=24) from the current time.
  74. The report provides ``max_revenue`` of the hourly order revenue sum,
  75. ``y-range`` as the labeling for the y-axis in a template and
  76. ``order_total_hourly``, a list of properties for hourly chunks.
  77. *segments* defines the number of labeling segments used for the y-axis
  78. when generating the y-axis labels (default=10).
  79. """
  80. # Get datetime for 24 hours agao
  81. time_now = now().replace(minute=0, second=0)
  82. start_time = time_now - timedelta(hours=hours - 1)
  83. orders_last_day = Order.objects.filter(date_placed__gt=start_time)
  84. order_total_hourly = []
  85. for hour in range(0, hours, 2):
  86. end_time = start_time + timedelta(hours=2)
  87. hourly_orders = orders_last_day.filter(date_placed__gt=start_time,
  88. date_placed__lt=end_time)
  89. total = hourly_orders.aggregate(
  90. Sum('total_incl_tax')
  91. )['total_incl_tax__sum'] or D('0.0')
  92. order_total_hourly.append({
  93. 'end_time': end_time,
  94. 'total_incl_tax': total
  95. })
  96. start_time = end_time
  97. max_value = max([x['total_incl_tax'] for x in order_total_hourly])
  98. divisor = 1
  99. while divisor < max_value / 50:
  100. divisor *= 10
  101. max_value = (max_value / divisor).quantize(D('1'), rounding=ROUND_UP)
  102. max_value *= divisor
  103. if max_value:
  104. segment_size = (max_value) / D('100.0')
  105. for item in order_total_hourly:
  106. item['percentage'] = int(item['total_incl_tax'] / segment_size)
  107. y_range = []
  108. y_axis_steps = max_value / D(str(segments))
  109. for idx in reversed(range(segments + 1)):
  110. y_range.append(idx * y_axis_steps)
  111. else:
  112. y_range = []
  113. for item in order_total_hourly:
  114. item['percentage'] = 0
  115. ctx = {
  116. 'order_total_hourly': order_total_hourly,
  117. 'max_revenue': max_value,
  118. 'y_range': y_range,
  119. }
  120. return ctx
  121. def get_stats(self):
  122. datetime_24hrs_ago = now() - timedelta(hours=24)
  123. orders = Order.objects.filter()
  124. orders_last_day = orders.filter(date_placed__gt=datetime_24hrs_ago)
  125. open_alerts = StockAlert.objects.filter(status=StockAlert.OPEN)
  126. closed_alerts = StockAlert.objects.filter(status=StockAlert.CLOSED)
  127. total_lines_last_day = Line.objects.filter(
  128. order__in=orders_last_day).count()
  129. stats = {
  130. 'total_orders_last_day': orders_last_day.count(),
  131. 'total_lines_last_day': total_lines_last_day,
  132. 'average_order_costs': orders_last_day.aggregate(
  133. Avg('total_incl_tax')
  134. )['total_incl_tax__avg'] or D('0.00'),
  135. 'total_revenue_last_day': orders_last_day.aggregate(
  136. Sum('total_incl_tax')
  137. )['total_incl_tax__sum'] or D('0.00'),
  138. 'hourly_report_dict': self.get_hourly_report(hours=24),
  139. 'total_customers_last_day': User.objects.filter(
  140. date_joined__gt=datetime_24hrs_ago,
  141. ).count(),
  142. 'total_open_baskets_last_day': self.get_open_baskets({
  143. 'date_created__gt': datetime_24hrs_ago
  144. }).count(),
  145. 'total_products': Product.objects.count(),
  146. 'total_open_stock_alerts': open_alerts.count(),
  147. 'total_closed_stock_alerts': closed_alerts.count(),
  148. 'total_site_offers': self.get_active_site_offers().count(),
  149. 'total_vouchers': self.get_active_vouchers().count(),
  150. 'total_promotions': self.get_number_of_promotions(),
  151. 'total_customers': User.objects.count(),
  152. 'total_open_baskets': self.get_open_baskets().count(),
  153. 'total_orders': orders.count(),
  154. 'total_lines': Line.objects.filter(order__in=orders).count(),
  155. 'total_revenue': orders.aggregate(
  156. Sum('total_incl_tax')
  157. )['total_incl_tax__sum'] or D('0.00'),
  158. 'order_status_breakdown': orders.order_by(
  159. 'status'
  160. ).values('status').annotate(freq=Count('id'))
  161. }
  162. return stats