Repository: saleor/saleor

Issue:
Problems with product image view in the dashboard
There are few problems with the view for editing images in the dashboard:
- colliding labels at the top ("Point of interest")
- checkbox "clear" is not working
- button to select a new image is not working

![image](https://user-images.githubusercontent.com/5421321/32446797-5c38d570-c30a-11e7-818e-36da250d9088.png)



Discussion:
I think showing the *disk* path to the image is a case of oversharing internal details.
Yes, we should only show the file name.
Checkbox and button are (probably) not working because something is covering it in template. If path (or in my new version, filename) is too short, clicking on it is not opening an image.

Resolving patch:
diff --git a/saleor/dashboard/product/widgets.py b/saleor/dashboard/product/widgets.py
--- a/saleor/dashboard/product/widgets.py
+++ b/saleor/dashboard/product/widgets.py
@@ -4,18 +4,19 @@
 
 
 class ImagePreviewFileInput(ClearableFileInputWithImagePreview):
-    template_with_initial_and_imagepreview = """
-    <div class="sizedimage-mod preview">
-        <div class="image-wrap outer">
-            <div class="point-stage" id="%(point_stage_id)s"
-                 data-image_preview_id="%(image_preview_id)s">
-                <div class="ppoi-point" id="%(ppoi_id)s"></div>
-            </div>
-            <div class="image-wrap inner">
-                %(image_preview)s
-            </div>
-        </div>
-    </div>"""
+    template_name = 'dashboard/product/product_image/versatile_image.html'
+
+    def get_context(self, name, value, attrs):
+        context = super(ImagePreviewFileInput, self).get_context(
+            name, value, attrs)
+        value = context['widget']['value']
+        if value:
+            value.display_value = self.get_filename_from_path(str(value))
+        return context
+
+    def get_filename_from_path(self, path):
+        filename = path.split('/')[-1]
+        return filename if filename else path
 
 
 class ImagePreviewWidget(SizedImageCenterpointWidgetMixIn, MultiWidget):
diff --git a/saleor/dashboard/templatetags/utils.py b/saleor/dashboard/templatetags/utils.py
--- a/saleor/dashboard/templatetags/utils.py
+++ b/saleor/dashboard/templatetags/utils.py
@@ -1,7 +1,7 @@
 from __future__ import unicode_literals
 
 from versatileimagefield.widgets import VersatileImagePPOIClickWidget
-from base64 import b64encode
+from ..product.widgets import ImagePreviewWidget
 
 try:
     from urllib.parse import urlencode
@@ -27,9 +27,22 @@ def construct_get_query(context, **params):
 
 @register.filter
 def is_versatile_image_ppoi_click_widget(field):
+    '''
+    This filter checks if image field widget is used when user wants to edit
+    existing product image.
+    '''
     return isinstance(field.field.widget, VersatileImagePPOIClickWidget)
 
 
+@register.filter
+def is_image_preview_widget(field):
+    '''
+    This filter checks if image field widget is used when user wants to add new
+    product image.
+    '''
+    return isinstance(field.field.widget, ImagePreviewWidget)
+
+
 @register.inclusion_tag('dashboard/product/product_variant/_image_select.html')
 def render_image_choice(field):
     choices = zip(field, field.field.queryset)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,9 +7,7 @@
 from io import BytesIO
 from PIL import Image
 
-from django.contrib.auth.models import AnonymousUser
 from django.contrib.sites.models import Site
-from django.utils.encoding import smart_text
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.contrib.auth.models import AnonymousUser, Group, Permission
 from django.core.files import File
diff --git a/tests/dashboard/test_permissions.py b/tests/dashboard/test_permissions.py
--- a/tests/dashboard/test_permissions.py
+++ b/tests/dashboard/test_permissions.py
@@ -175,6 +175,24 @@ def test_staff_cant_view_products_attribute_delete(
     assert response.status_code == 302
 
 
+def test_admin_can_view_product_image_update(admin_client, product_with_image):
+    product_image = product_with_image.images.all()[0]
+    url = reverse('dashboard:product-image-update',
+                  kwargs={'img_pk': product_image.pk,
+                          'product_pk': product_with_image.pk})
+    response = admin_client.get(url)
+    assert response.status_code == 200
+
+
+def test_staff_cant_view_product_image_update(staff_client, product_with_image):
+    product_image = product_with_image.images.all()[0]
+    url = reverse('dashboard:product-image-update',
+                  kwargs={'img_pk': product_image.pk,
+                          'product_pk': product_with_image.pk})
+    response = staff_client.get(url)
+    assert response.status_code == 302
+
+
 def test_admin_can_view_shipping_methods_list(admin_client):
     response = admin_client.get(reverse('dashboard:shipping-methods'))
     assert response.status_code == 200
diff --git a/tests/dashboard/test_product.py b/tests/dashboard/test_product.py
--- a/tests/dashboard/test_product.py
+++ b/tests/dashboard/test_product.py
@@ -1,16 +1,17 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
+from io import BytesIO
 import json
 import pytest
-from django import forms
+from PIL import Image
+from django.core.files.uploadedfile import SimpleUploadedFile
 from django.core.urlresolvers import reverse
 from django.utils.encoding import smart_text
-from mock import Mock
 
 from saleor.dashboard.product import ProductBulkAction
 from saleor.dashboard.product.forms import (
-    ProductBulkUpdate, ProductClassForm, ProductClassSelectorForm, ProductForm)
+    ProductBulkUpdate, ProductClassForm, ProductForm)
 from saleor.product.models import (
     Product, ProductAttribute, ProductClass, ProductImage, ProductVariant,
     Stock, StockLocation)
@@ -20,6 +21,16 @@
 HTTP_REDIRECTION = 302
 
 
+def create_image():
+    img_data = BytesIO()
+    image = Image.new('RGBA', size=(1, 1), color=(255, 0, 0, 0))
+    image.save(img_data, format='JPEG')
+    image_name = 'product2'
+    image = SimpleUploadedFile(
+        image_name + '.jpg', img_data.getvalue(), 'image/png')
+    return image, image_name
+
+
 @pytest.mark.integration
 @pytest.mark.django_db
 def test_stock_record_update_works(admin_client, product_in_stock):
@@ -277,7 +288,8 @@ def test_view_product_image_delete(admin_client, product_with_image):
     response = admin_client.post(url)
     assert response.status_code == HTTP_REDIRECTION
     assert not ProductImage.objects.filter(pk=product_image.pk)
-    
+
+
 def test_view_reorder_product_images(admin_client, product_with_images):
     order_before = [img.pk for img in product_with_images.images.all()]
     ordered_images = list(reversed(order_before))
@@ -308,6 +320,60 @@ def test_view_invalid_reorder_product_images(
     assert 'ordered_images' in resp_decoded['error']
 
 
+def test_view_product_image_add(admin_client, product_with_image):
+    assert len(ProductImage.objects.all()) == 1
+    assert len(product_with_image.images.all()) == 1
+    url = reverse('dashboard:product-image-add',
+                  kwargs={'product_pk': product_with_image.pk})
+    response = admin_client.get(url)
+    assert response.status_code == 200
+    image, image_name = create_image()
+    data = {'image_0': image, 'alt': ['description']}
+    response = admin_client.post(url, data, follow=True)
+    assert response.status_code == 200
+    assert len(ProductImage.objects.all()) == 2
+    product_with_image.refresh_from_db()
+    images = product_with_image.images.all()
+    assert len(images) == 2
+    assert image_name in images[1].image.name
+    assert images[1].alt == 'description'
+
+
+def test_view_product_image_edit_same_image_add_description(
+        admin_client, product_with_image):
+    assert len(product_with_image.images.all()) == 1
+    product_image = product_with_image.images.all()[0]
+    url = reverse('dashboard:product-image-update',
+                  kwargs={'img_pk': product_image.pk,
+                          'product_pk': product_with_image.pk})
+    response = admin_client.get(url)
+    assert response.status_code == 200
+    data = {'image_1': ['0.49x0.59'], 'alt': ['description']}
+    response = admin_client.post(url, data, follow=True)
+    assert response.status_code == 200
+    assert len(product_with_image.images.all()) == 1
+    product_image.refresh_from_db()
+    assert product_image.alt == 'description'
+
+
+def test_view_product_image_edit_new_image(admin_client, product_with_image):
+    assert len(product_with_image.images.all()) == 1
+    product_image = product_with_image.images.all()[0]
+    url = reverse('dashboard:product-image-update',
+                  kwargs={'img_pk': product_image.pk,
+                          'product_pk': product_with_image.pk})
+    response = admin_client.get(url)
+    assert response.status_code == 200
+    image, image_name = create_image()
+    data = {'image_0': image, 'alt': ['description']}
+    response = admin_client.post(url, data, follow=True)
+    assert response.status_code == 200
+    assert len(product_with_image.images.all()) == 1
+    product_image.refresh_from_db()
+    assert image_name in product_image.image.name
+    assert product_image.alt == 'description'
+
+
 def perform_bulk_action(product_list, action):
     """Perform given bulk action on given product list."""
     data = {'action': action, 'products': [p.pk for p in product_list]}