Repository: saleor/saleor

Issue:
Add placeholder image to empty search results page
Search results page should have an image as it was designed.


Discussion:
@subgrafik Do we have design for this? Could you provide some placeholder image?

Resolving patch:
diff --git a/saleor/cart/views.py b/saleor/cart/views.py
--- a/saleor/cart/views.py
+++ b/saleor/cart/views.py
@@ -9,6 +9,7 @@
 
 from ..core.utils import to_local_currency, get_user_shipping_country
 from ..product.models import ProductVariant
+from ..product.templatetags.product_images import get_thumbnail
 from ..shipping.utils import get_shipment_options
 from .forms import ReplaceCartLineForm, CountryForm
 from .models import Cart
@@ -114,7 +115,7 @@ def prepare_line_data(line):
             'variant': line.variant.name,
             'quantity': line.quantity,
             'attributes': line.variant.display_variant(attributes),
-            'image': first_image.url if first_image else None,
+            'image': first_image,
             'price_per_item': currencyfmt(
                 price_per_item.gross, price_per_item.currency),
             'line_total': currencyfmt(line_total.gross, line_total.currency),
diff --git a/saleor/graphql/api.py b/saleor/graphql/api.py
--- a/saleor/graphql/api.py
+++ b/saleor/graphql/api.py
@@ -10,6 +10,7 @@
 from ..product.models import (AttributeChoiceValue, Category, Product,
                               ProductAttribute, ProductImage, ProductVariant)
 from ..product.utils import get_availability
+from ..product.templatetags.product_images import product_first_image
 from .scalars import AttributesFilterScalar
 from .utils import (CategoryAncestorsCache, DjangoPkInterface)
 
@@ -37,7 +38,7 @@ class ProductAvailabilityType(graphene.ObjectType):
 
 class ProductType(DjangoObjectType):
     url = graphene.String()
-    image_url = graphene.String()
+    thumbnail_url = graphene.String()
     images = graphene.List(lambda: ProductImageType)
     variants = graphene.List(lambda: ProductVariantType)
     availability = graphene.Field(lambda: ProductAvailabilityType)
@@ -47,9 +48,8 @@ class Meta:
         model = Product
         interfaces = (relay.Node, DjangoPkInterface)
 
-    def resolve_image_url(self, args, context, info):
-        image = self.images.first()
-        return image.image.crop['400x400'].url if image else None
+    def resolve_thumbnail_url(self, args, context, info):
+        return product_first_image(self, '400x400')
 
     def resolve_images(self, args, context, info):
         return self.images.all()
@@ -220,13 +220,15 @@ class PriceRangeType(graphene.ObjectType):
     min_price = graphene.Field(lambda: PriceType)
 
 
-class Viewer(graphene.ObjectType):
-    category = graphene.Field(
-        CategoryType,
-        pk=graphene.Argument(graphene.Int, required=True))
+class Query(graphene.ObjectType):
     attributes = graphene.List(
         ProductAttributeType,
         category_pk=graphene.Argument(graphene.Int, required=False))
+    category = graphene.Field(
+        CategoryType,
+        pk=graphene.Argument(graphene.Int, required=True))
+    node = relay.Node.Field()
+    root = graphene.Field(lambda: Query)
     debug = graphene.Field(DjangoDebug, name='__debug')
 
     def resolve_category(self, args, context, info):
@@ -254,13 +256,10 @@ def resolve_attributes(self, args, context, info):
                 Q(product_variants_class__in=product_classes))
         return queryset.distinct()
 
-
-class Query(graphene.ObjectType):
-    viewer = graphene.Field(Viewer)
-    node = relay.Node.Field()
-
-    def resolve_viewer(self, args, context, info):
-        return Viewer()
+    def resolve_root(self, args, context, info):
+        # Re-expose the root query object. Workaround for the issue in Relay:
+        # https://github.com/facebook/relay/issues/112
+        return Query()
 
 
 schema = graphene.Schema(Query)
diff --git a/saleor/urls.py b/saleor/urls.py
--- a/saleor/urls.py
+++ b/saleor/urls.py
@@ -27,7 +27,7 @@
     url(r'^cart/', include(cart_urls, namespace='cart')),
     url(r'^checkout/', include(checkout_urls, namespace='checkout')),
     url(r'^dashboard/', include(dashboard_urls, namespace='dashboard')),
-    url(r'^graphql', GraphQLView.as_view(graphiql=True)),
+    url(r'^graphql', GraphQLView.as_view(graphiql=settings.DEBUG)),
     url(r'^order/', include(order_urls, namespace='order')),
     url(r'^products/', include(product_urls, namespace='product')),
     url(r'^profile/', include(userprofile_urls, namespace='profile')),


Test patch:
diff --git a/tests/test_graphql.py b/tests/test_graphql.py
--- a/tests/test_graphql.py
+++ b/tests/test_graphql.py
@@ -11,7 +11,6 @@ def get_content(response):
 def assert_success(content):
     assert 'errors' not in content
     assert 'data' in content
-    assert 'viewer' in content['data']
 
 
 def assert_corresponding_fields(iterable_data, queryset, fields):
@@ -26,22 +25,20 @@ def test_category_query(client, product_in_stock):
     category = Category.objects.first()
     query = """
         query {
-            viewer {
-                category(pk: %(category_pk)s) {
-                    pk
-                    name
-                    productsCount
-                    ancestors { name }
-                    children { name }
-                    siblings { name }
-                }
+            category(pk: %(category_pk)s) {
+                pk
+                name
+                productsCount
+                ancestors { name }
+                children { name }
+                siblings { name }
             }
         }
     """ % {'category_pk': category.pk}
     response = client.post('/graphql/', {'query': query})
     content = get_content(response)
     assert_success(content)
-    category_data = content['data']['viewer']['category']
+    category_data = content['data']['category']
     assert category_data is not None
     assert int(category_data['pk']) == category.pk
     assert category_data['name'] == category.name
@@ -60,28 +57,26 @@ def test_product_query(client, product_in_stock):
     product = category.products.first()
     query = """
         query {
-            viewer {
-                category(pk: %(category_pk)s) {
-                    products {
-                        edges {
-                            node {
-                                pk
+            category(pk: %(category_pk)s) {
+                products {
+                    edges {
+                        node {
+                            pk
+                            name
+                            url
+                            thumbnailUrl
+                            images { url }
+                            variants {
                                 name
-                                url
-                                imageUrl
-                                images { url }
-                                variants {
-                                    name
-                                    stockQuantity
-                                }
-                                availability {
-                                    available,
-                                    priceRange {
-                                        minPrice {
-                                            gross
-                                            net
-                                            currency
-                                        }
+                                stockQuantity
+                            }
+                            availability {
+                                available,
+                                priceRange {
+                                    minPrice {
+                                        gross
+                                        net
+                                        currency
                                     }
                                 }
                             }
@@ -94,8 +89,8 @@ def test_product_query(client, product_in_stock):
     response = client.post('/graphql/', {'query': query})
     content = get_content(response)
     assert_success(content)
-    assert content['data']['viewer']['category'] is not None
-    product_edges_data = content['data']['viewer']['category']['products']['edges']
+    assert content['data']['category'] is not None
+    product_edges_data = content['data']['category']['products']['edges']
     assert len(product_edges_data) == category.products.count()
     product_data = product_edges_data[0]['node']
     assert int(product_data['pk']) == product.pk
@@ -113,13 +108,11 @@ def test_filter_product_by_attributes(client, product_in_stock):
     filter_by = "%s:%s" % (product_attr.name, attr_value.slug)
     query = """
         query {
-            viewer {
-                category(pk: %(category_pk)s) {
-                    products(attributes: ["%(filter_by)s"]) {
-                        edges {
-                            node {
-                                name
-                            }
+            category(pk: %(category_pk)s) {
+                products(attributes: ["%(filter_by)s"]) {
+                    edges {
+                        node {
+                            name
                         }
                     }
                 }
@@ -129,7 +122,7 @@ def test_filter_product_by_attributes(client, product_in_stock):
     response = client.post('/graphql/', {'query': query})
     content = get_content(response)
     assert_success(content)
-    product_data = content['data']['viewer']['category']['products']['edges'][0]['node']
+    product_data = content['data']['category']['products']['edges'][0]['node']
     assert product_data['name'] == product_in_stock.name
 
 
@@ -138,16 +131,14 @@ def test_attributes_query(client, product_in_stock):
     attributes = ProductAttribute.objects.prefetch_related('values')
     query = """
         query {
-            viewer {
-                attributes {
+            attributes {
+                pk
+                name
+                display
+                values {
                     pk
-                    name
                     display
-                    values {
-                        pk
-                        display
-                        slug
-                    }
+                    slug
                 }
             }
         }
@@ -155,7 +146,7 @@ def test_attributes_query(client, product_in_stock):
     response = client.post('/graphql/', {'query': query})
     content = get_content(response)
     assert_success(content)
-    attributes_data = content['data']['viewer']['attributes']
+    attributes_data = content['data']['attributes']
     assert_corresponding_fields(attributes_data, attributes, ['pk', 'name'])
 
 
@@ -164,16 +155,14 @@ def test_attributes_in_category_query(client, product_in_stock):
     category = Category.objects.first()
     query = """
         query {
-            viewer {
-                attributes(categoryPk: %(category_pk)s) {
+            attributes(categoryPk: %(category_pk)s) {
+                pk
+                name
+                display
+                values {
                     pk
-                    name
                     display
-                    values {
-                        pk
-                        display
-                        slug
-                    }
+                    slug
                 }
             }
         }