You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

views.py 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. from django.conf import settings
  2. from django.http import HttpResponsePermanentRedirect
  3. from django.shortcuts import get_object_or_404
  4. from django.views.generic import ListView, DetailView
  5. from django.db.models import get_model
  6. from django.utils.translation import ugettext_lazy as _
  7. from oscar.core.loading import get_class
  8. from oscar.apps.catalogue.signals import product_viewed, product_search
  9. Product = get_model('catalogue', 'product')
  10. ProductReview = get_model('reviews', 'ProductReview')
  11. Category = get_model('catalogue', 'category')
  12. ProductAlert = get_model('customer', 'ProductAlert')
  13. ProductAlertForm = get_class('customer.forms', 'ProductAlertForm')
  14. class ProductDetailView(DetailView):
  15. context_object_name = 'product'
  16. model = Product
  17. view_signal = product_viewed
  18. template_folder = "catalogue"
  19. def get(self, request, **kwargs):
  20. """
  21. Ensures that the correct URL is used before rendering a response
  22. """
  23. self.object = product = self.get_object()
  24. if product.is_variant:
  25. return HttpResponsePermanentRedirect(
  26. product.parent.get_absolute_url())
  27. correct_path = product.get_absolute_url()
  28. if correct_path != request.path:
  29. return HttpResponsePermanentRedirect(correct_path)
  30. response = super(ProductDetailView, self).get(request, **kwargs)
  31. self.send_signal(request, response, product)
  32. return response
  33. def get_object(self, queryset=None):
  34. # Check if self.object is already set to prevent unnecessary DB calls
  35. return getattr(
  36. self, 'object', super(ProductDetailView, self).get_object(queryset))
  37. def get_context_data(self, **kwargs):
  38. ctx = super(ProductDetailView, self).get_context_data(**kwargs)
  39. ctx['reviews'] = self.get_reviews()
  40. ctx['alert_form'] = self.get_alert_form()
  41. ctx['has_active_alert'] = self.get_alert_status()
  42. return ctx
  43. def get_alert_status(self):
  44. # Check if this user already have an alert for this product
  45. has_alert = False
  46. if self.request.user.is_authenticated():
  47. alerts = ProductAlert.objects.filter(
  48. product=self.object, user=self.request.user,
  49. status=ProductAlert.ACTIVE)
  50. has_alert = alerts.count() > 0
  51. return has_alert
  52. def get_alert_form(self):
  53. return ProductAlertForm(
  54. user=self.request.user, product=self.object)
  55. def get_reviews(self):
  56. return self.object.reviews.filter(status=ProductReview.APPROVED)
  57. def send_signal(self, request, response, product):
  58. self.view_signal.send(
  59. sender=self, product=product, user=request.user, request=request,
  60. response=response)
  61. def get_template_names(self):
  62. """
  63. Return a list of possible templates.
  64. We try 2 options before defaulting to catalogue/detail.html:
  65. 1). detail-for-upc-<upc>.html
  66. 2). detail-for-class-<classname>.html
  67. This allows alternative templates to be provided for a per-product
  68. and a per-item-class basis.
  69. """
  70. return [
  71. '%s/detail-for-upc-%s.html' % (
  72. self.template_folder, self.object.upc),
  73. '%s/detail-for-class-%s.html' % (
  74. self.template_folder, self.object.get_product_class().slug),
  75. '%s/detail.html' % (self.template_folder)]
  76. def get_product_base_queryset():
  77. """
  78. Return ``QuerySet`` for product model with related
  79. content pre-loaded. The ``QuerySet`` returns unfiltered
  80. results for further filtering.
  81. """
  82. return Product.browsable.select_related(
  83. 'product_class',
  84. ).prefetch_related(
  85. 'variants',
  86. 'product_options',
  87. 'product_class__options',
  88. 'stockrecord',
  89. 'images',
  90. ).all()
  91. class ProductCategoryView(ListView):
  92. """
  93. Browse products in a given category
  94. Category URLs used to be based on solely the slug. Renaming the category
  95. or any of the parent categories would break the URL. Hence, the new URLs
  96. consist of both the slug and category PK (compare product URLs).
  97. The legacy way still works to not break existing systems.
  98. """
  99. context_object_name = "products"
  100. template_name = 'catalogue/browse.html'
  101. paginate_by = settings.OSCAR_PRODUCTS_PER_PAGE
  102. def get_object(self):
  103. if 'pk' in self.kwargs:
  104. self.category = get_object_or_404(Category, pk=self.kwargs['pk'])
  105. else:
  106. self.category = get_object_or_404(Category,
  107. slug=self.kwargs['category_slug'])
  108. def get(self, request, *args, **kwargs):
  109. self.get_object()
  110. correct_path = self.category.get_absolute_url()
  111. if correct_path != request.path:
  112. return HttpResponsePermanentRedirect(correct_path)
  113. self.categories = self.get_categories()
  114. return super(ProductCategoryView, self).get(request, *args, **kwargs)
  115. def get_categories(self):
  116. """
  117. Return a list of the current category and it's ancestors
  118. """
  119. categories = list(self.category.get_descendants())
  120. categories.append(self.category)
  121. return categories
  122. def get_context_data(self, **kwargs):
  123. context = super(ProductCategoryView, self).get_context_data(**kwargs)
  124. context['categories'] = self.categories
  125. context['category'] = self.category
  126. context['summary'] = self.category.name
  127. return context
  128. def get_queryset(self):
  129. return get_product_base_queryset().filter(
  130. categories__in=self.categories
  131. ).distinct()
  132. class ProductListView(ListView):
  133. """
  134. A list of products
  135. """
  136. context_object_name = "products"
  137. template_name = 'catalogue/browse.html'
  138. paginate_by = settings.OSCAR_PRODUCTS_PER_PAGE
  139. search_signal = product_search
  140. model = Product
  141. def get_search_query(self):
  142. q = self.request.GET.get('q', None)
  143. return q.strip() if q else q
  144. def get_queryset(self):
  145. q = self.get_search_query()
  146. if q:
  147. # Send signal to record the view of this product
  148. self.search_signal.send(sender=self, query=q, user=self.request.user)
  149. return get_product_base_queryset().filter(title__icontains=q)
  150. else:
  151. return get_product_base_queryset()
  152. def get_context_data(self, **kwargs):
  153. context = super(ProductListView, self).get_context_data(**kwargs)
  154. q = self.get_search_query()
  155. if not q:
  156. context['summary'] = _('All products')
  157. else:
  158. context['summary'] = _("Products matching '%(query)s'") % {'query': q}
  159. context['search_term'] = q
  160. return context