Repository: saleor/saleor

Issue:
Variant attributes should be copied as well when order is placed
as with shipping address in https://github.com/mirumee/saleor/issues/503

I think ProductVariant's attributes should be copied.



Discussion:
Most stores only need the SKU to fulfil an order. I'm not sure we need to copy all attribute values by default.

It came up when I needed to display what a user bought for himself.

Some of the attributes were important enough to show.  
Well I just wanted to share my thoughts.

We're open to discuss this. We have one store that also wants to show sizes as a separate field in order details (instead of part of the product's name).

@artursmet Want to share your insight?

In my opinion we should also copy the attributes. As you mentioned, there are stores that have all variants with the same names, only size or color differ. 
We can implement it by the way with the fix for shipping address you mentioned above.


Resolving patch:
diff --git a/saleor/order/models.py b/saleor/order/models.py
--- a/saleor/order/models.py
+++ b/saleor/order/models.py
@@ -262,7 +262,7 @@ def add_items_from_partition(self, partition, discounts=None):
                 product=product_variant.product,
                 quantity=quantity,
                 unit_price_net=price.net,
-                product_name=smart_text(product_variant),
+                product_name=product_variant.display_product(),
                 product_sku=product_variant.sku,
                 unit_price_gross=price.gross,
                 stock=stock,
diff --git a/saleor/product/forms.py b/saleor/product/forms.py
--- a/saleor/product/forms.py
+++ b/saleor/product/forms.py
@@ -4,6 +4,7 @@
 
 from django import forms
 from django.template.loader import render_to_string
+from django.utils.encoding import smart_text
 from django.utils.translation import pgettext_lazy
 from django_prices.templatetags.prices_i18n import gross
 
@@ -15,7 +16,7 @@ class VariantChoiceField(forms.ModelChoiceField):
 
     def label_from_instance(self, obj):
         attributes = obj.product.product_class.variant_attributes.all()
-        variant_label = obj.display_variant(attributes)
+        variant_label = smart_text(obj)
         label = '%(variant_label)s - %(price)s' % {
             'variant_label': variant_label,
             'price': gross(obj.get_price(discounts=self.discounts))}
diff --git a/saleor/product/models/base.py b/saleor/product/models/base.py
--- a/saleor/product/models/base.py
+++ b/saleor/product/models/base.py
@@ -12,6 +12,7 @@
 from django.utils.encoding import python_2_unicode_compatible, smart_text
 from django.utils.text import slugify
 from django.utils.translation import pgettext_lazy
+from django.utils import six
 from django_prices.models import PriceField
 from mptt.managers import TreeManager
 from mptt.models import MPTTModel
@@ -204,7 +205,7 @@ class Meta:
         app_label = 'product'
 
     def __str__(self):
-        return self.name or self.sku
+        return self.name or self.display_variant()
 
     def get_weight(self):
         return self.weight_override or self.product.weight
@@ -257,15 +258,18 @@ def set_attribute(self, pk, value_pk):
     def display_variant(self, attributes=None):
         if attributes is None:
             attributes = self.product.product_class.variant_attributes.all()
-        values = get_attributes_display_map(self, attributes).values()
+        values = get_attributes_display_map(self, attributes)
         if values:
-            return ', '.join([smart_text(value) for value in values])
+            return ', '.join(
+                ['%s: %s' % (smart_text(attributes.get(id=int(key))),
+                              smart_text(value))
+                 for (key, value) in six.iteritems(values)])
         else:
-            return smart_text(self)
+            return smart_text(self.sku)
 
-    def display_product(self, attributes=None):
+    def display_product(self):
         return '%s (%s)' % (smart_text(self.product),
-                            self.display_variant(attributes=attributes))
+                            smart_text(self))
 
     def get_first_image(self):
         return self.product.get_first_image()


Test patch: