選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

views.py 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. from io import TextIOWrapper
  2. from django.conf import settings
  3. from django.contrib import messages
  4. from django.core import exceptions
  5. from django.db.models import Count
  6. from django.http import HttpResponseRedirect
  7. from django.shortcuts import HttpResponse, get_object_or_404
  8. from django.template.loader import render_to_string
  9. from django.urls import reverse
  10. from django.utils.translation import gettext_lazy as _
  11. from django.utils.translation import ngettext
  12. from django.views.generic import (
  13. CreateView, DeleteView, ListView, UpdateView, View)
  14. from oscar.core.loading import get_classes, get_model
  15. from oscar.views.generic import BulkEditMixin
  16. Range = get_model('offer', 'Range')
  17. RangeProduct = get_model('offer', 'RangeProduct')
  18. RangeProductFileUpload = get_model('offer', 'RangeProductFileUpload')
  19. Product = get_model('catalogue', 'Product')
  20. RangeForm, RangeProductForm = get_classes('dashboard.ranges.forms',
  21. ['RangeForm', 'RangeProductForm'])
  22. class RangeListView(ListView):
  23. model = Range
  24. context_object_name = 'ranges'
  25. template_name = 'oscar/dashboard/ranges/range_list.html'
  26. paginate_by = settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE
  27. class RangeCreateView(CreateView):
  28. model = Range
  29. template_name = 'oscar/dashboard/ranges/range_form.html'
  30. form_class = RangeForm
  31. def get_success_url(self):
  32. if 'action' in self.request.POST:
  33. return reverse('dashboard:range-products',
  34. kwargs={'pk': self.object.id})
  35. else:
  36. msg = render_to_string(
  37. 'oscar/dashboard/ranges/messages/range_saved.html',
  38. {'range': self.object})
  39. messages.success(self.request, msg, extra_tags='safe noicon')
  40. return reverse('dashboard:range-list')
  41. def get_context_data(self, **kwargs):
  42. ctx = super().get_context_data(**kwargs)
  43. ctx['title'] = _("Create range")
  44. return ctx
  45. class RangeUpdateView(UpdateView):
  46. model = Range
  47. template_name = 'oscar/dashboard/ranges/range_form.html'
  48. form_class = RangeForm
  49. def get_object(self):
  50. obj = super().get_object()
  51. if not obj.is_editable:
  52. raise exceptions.PermissionDenied("Not allowed")
  53. return obj
  54. def get_success_url(self):
  55. if 'action' in self.request.POST:
  56. return reverse('dashboard:range-products',
  57. kwargs={'pk': self.object.id})
  58. else:
  59. msg = render_to_string(
  60. 'oscar/dashboard/ranges/messages/range_saved.html',
  61. {'range': self.object})
  62. messages.success(self.request, msg, extra_tags='safe noicon')
  63. return reverse('dashboard:range-list')
  64. def get_context_data(self, **kwargs):
  65. ctx = super().get_context_data(**kwargs)
  66. ctx['range'] = self.object
  67. ctx['title'] = self.object.name
  68. return ctx
  69. class RangeDeleteView(DeleteView):
  70. model = Range
  71. template_name = 'oscar/dashboard/ranges/range_delete.html'
  72. context_object_name = 'range'
  73. def get_success_url(self):
  74. messages.warning(self.request, _("Range deleted"))
  75. return reverse('dashboard:range-list')
  76. class RangeProductListView(BulkEditMixin, ListView):
  77. model = Product
  78. template_name = 'oscar/dashboard/ranges/range_product_list.html'
  79. context_object_name = 'products'
  80. actions = ('remove_selected_products', 'add_products')
  81. form_class = RangeProductForm
  82. paginate_by = settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE
  83. def post(self, request, *args, **kwargs):
  84. self.object_list = self.get_queryset()
  85. if request.POST.get('action', None) == 'add_products':
  86. return self.add_products(request)
  87. return super().post(request, *args, **kwargs)
  88. def get_range(self):
  89. if not hasattr(self, '_range'):
  90. self._range = get_object_or_404(Range, id=self.kwargs['pk'])
  91. return self._range
  92. def get_queryset(self):
  93. products = self.get_range().all_products()
  94. return products.order_by('rangeproduct__display_order')
  95. def get_context_data(self, **kwargs):
  96. ctx = super().get_context_data(**kwargs)
  97. range = self.get_range()
  98. ctx['range'] = range
  99. if 'form' not in ctx:
  100. ctx['form'] = self.form_class(range)
  101. return ctx
  102. def remove_selected_products(self, request, products):
  103. range = self.get_range()
  104. for product in products:
  105. range.remove_product(product)
  106. num_products = len(products)
  107. messages.success(
  108. request,
  109. ngettext("Removed %d product from range", "Removed %d products from range", num_products) % num_products
  110. )
  111. return HttpResponseRedirect(self.get_success_url(request))
  112. def add_products(self, request):
  113. range = self.get_range()
  114. form = self.form_class(range, request.POST, request.FILES)
  115. if not form.is_valid():
  116. ctx = self.get_context_data(form=form,
  117. object_list=self.object_list)
  118. return self.render_to_response(ctx)
  119. self.handle_query_products(request, range, form)
  120. self.handle_file_products(request, range, form)
  121. return HttpResponseRedirect(self.get_success_url(request))
  122. def handle_query_products(self, request, range, form):
  123. products = form.get_products()
  124. if not products:
  125. return
  126. for product in products:
  127. range.add_product(product)
  128. num_products = len(products)
  129. messages.success(
  130. request,
  131. ngettext("%d product added to range", "%d products added to range", num_products) % num_products
  132. )
  133. dupe_skus = form.get_duplicate_skus()
  134. if dupe_skus:
  135. messages.warning(
  136. request,
  137. _("The products with SKUs or UPCs matching %s are already "
  138. "in this range") % ", ".join(dupe_skus))
  139. missing_skus = form.get_missing_skus()
  140. if missing_skus:
  141. messages.warning(
  142. request,
  143. _("No product(s) were found with SKU or UPC matching %s") %
  144. ", ".join(missing_skus))
  145. self.check_imported_products_sku_duplicates(request, products)
  146. def handle_file_products(self, request, range, form):
  147. if 'file_upload' not in request.FILES:
  148. return
  149. f = request.FILES['file_upload']
  150. upload = self.create_upload_object(request, range, f)
  151. products = upload.process(TextIOWrapper(f, encoding=request.encoding))
  152. if not upload.was_processing_successful():
  153. messages.error(request, upload.error_message)
  154. else:
  155. msg = render_to_string(
  156. 'oscar/dashboard/ranges/messages/range_products_saved.html',
  157. {'range': range,
  158. 'upload': upload})
  159. messages.success(request, msg, extra_tags='safe noicon block')
  160. self.check_imported_products_sku_duplicates(request, products)
  161. def create_upload_object(self, request, range, f):
  162. upload = RangeProductFileUpload.objects.create(
  163. range=range,
  164. uploaded_by=request.user,
  165. filepath=f.name,
  166. size=f.size
  167. )
  168. return upload
  169. def check_imported_products_sku_duplicates(self, request, queryset):
  170. dupe_sku_products = queryset.values('stockrecords__partner_sku')\
  171. .annotate(total=Count('stockrecords__partner_sku'))\
  172. .filter(total__gt=1).order_by('stockrecords__partner_sku')
  173. if dupe_sku_products:
  174. dupe_skus = [p['stockrecords__partner_sku'] for p in dupe_sku_products]
  175. messages.warning(
  176. request,
  177. _("There are more than one product with SKU %s") %
  178. ", ".join(dupe_skus)
  179. )
  180. class RangeReorderView(View):
  181. def post(self, request, pk):
  182. order = dict(request.POST).get('product')
  183. self._save_page_order(order)
  184. return HttpResponse(status=200)
  185. def _save_page_order(self, order):
  186. """
  187. Save the order of the products within range.
  188. """
  189. range_products = RangeProduct.objects.filter(
  190. range_id=self.kwargs['pk'], product_id__in=order
  191. )
  192. for range_product in range_products:
  193. range_product.display_order = order.index(
  194. str(range_product.product_id)
  195. )
  196. RangeProduct.objects.bulk_update(range_products, ["display_order"])