Repository: django-oscar/django-oscar

Issue:
Variant products don't need their own detail page
At the moment, the detail page for a variant product can raise 500 (see trace from demo site below).  Variant products should redirect to the group product page.  Also the offending template lookup code should use `get_product_class` so that it picks up the parent's product class.

Trace:

```
Traceback (most recent call last):

  File "/var/www/oscar/virtualenvs/demo/lib/python2.6/site-packages/Django-1.4.1-py2.6.egg/django/core/handlers/base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)

  File "/var/www/oscar/virtualenvs/demo/lib/python2.6/site-packages/Django-1.4.1-py2.6.egg/django/views/generic/base.py", line 48, in view
    return self.dispatch(request, *args, **kwargs)

  File "/var/www/oscar/virtualenvs/demo/lib/python2.6/site-packages/Django-1.4.1-py2.6.egg/django/views/generic/base.py", line 69, in dispatch
    return handler(request, *args, **kwargs)

  File "/var/www/oscar/builds/demo/oscar/apps/catalogue/views.py", line 62, in get
    response = super(ProductDetailView, self).get(request, **kwargs)

  File "/var/www/oscar/virtualenvs/demo/lib/python2.6/site-packages/Django-1.4.1-py2.6.egg/django/views/generic/detail.py", line 100, in get
    return self.render_to_response(context)

  File "/var/www/oscar/virtualenvs/demo/lib/python2.6/site-packages/Django-1.4.1-py2.6.egg/django/views/generic/base.py", line 95, in render_to_response
    template = self.get_template_names(),

  File "/var/www/oscar/builds/demo/oscar/apps/catalogue/views.py", line 83, in get_template_names
    '%s/detail-for-class-%s.html' % (self.template_folder, product.product_class.name.lower()),

AttributeError: 'NoneType' object has no attribute 'name'

```



Discussion:
What about variants that are changing the apperance of the product? For example red, green or blue T-shirt? In this case, idealy, we want display different images for each variant

The fact that we store separate products for each possible variant of a product should be invisible to the front end user. Therefore having a product detail page just for the parent product makes sense. Not all websites will have different images for different variants, most will just have a colour swatch and a drop down of sizes as it makes it easier to manage. However if you want to display different images for different variants this can still be done by passing a dict of product image urls to the template and using a javascript user interface to display different images based on the users selection.


Resolving patch:
diff --git a/oscar/apps/catalogue/views.py b/oscar/apps/catalogue/views.py
--- a/oscar/apps/catalogue/views.py
+++ b/oscar/apps/catalogue/views.py
@@ -23,6 +23,10 @@ class ProductDetailView(DetailView):
     def get(self, request, **kwargs):
         # Ensure that the correct URL is used
         product = self.get_object()
+
+        if product.is_variant:
+            return HttpResponsePermanentRedirect(product.parent.get_absolute_url())
+
         correct_path = product.get_absolute_url()
         if correct_path != request.path:
             return HttpResponsePermanentRedirect(correct_path)
@@ -83,7 +87,7 @@ def get_template_names(self):
             '%s/detail-for-upc-%s.html' % (
                 self.template_folder, product.upc),
             '%s/detail-for-class-%s.html' % (
-                self.template_folder, product.product_class.name.lower()),
+                self.template_folder, product.get_product_class().name.lower()),
             '%s/detail.html' % (self.template_folder)]
 
 


Test patch:
diff --git a/tests/functional/catalogue/catalogue_tests.py b/tests/functional/catalogue/catalogue_tests.py
--- a/tests/functional/catalogue/catalogue_tests.py
+++ b/tests/functional/catalogue/catalogue_tests.py
@@ -18,6 +18,23 @@ def test_enforces_canonical_url(self):
         response = self.app.get(wrong_url)
         self.assertEquals(httplib.MOVED_PERMANENTLY, response.status_code)
 
+    def test_variant_to_parent_redirect(self):
+        parent_product = create_product()
+        kwargs = {'product_slug': parent_product.slug,
+                  'pk': parent_product.id}
+        parent_product_url = reverse('catalogue:detail', kwargs=kwargs)
+
+        variant = create_product(title="Variant 1", parent=parent_product)
+        kwargs = {'product_slug': variant.slug,
+                  'pk': variant.id}
+        variant_url = reverse('catalogue:detail', kwargs=kwargs)
+
+        response = self.app.get(parent_product_url)
+        self.assertEquals(httplib.OK, response.status_code)
+
+        response = self.app.get(variant_url)
+        self.assertEquals(httplib.MOVED_PERMANENTLY, response.status_code)
+
 
 class TestProductListView(WebTestCase):