Repository: saleor/saleor
Pull request: Toggle publish product by post request
fixes #1302 

Problem statement:
Switch to publish/unpublish products uses GET instead of POST
The switch that lives in the product details dashboard does GET requests to change the product state (`product_toggle_is_published` view). We should always use POST for requests that modify the state or trigger actions.

Hints from issue discussion:

Commit message: Toggle publish product by post request

Patch to review:
diff --git a/saleor/dashboard/product/views.py b/saleor/dashboard/product/views.py
index 0c985c88290..d62c99e302f 100644
--- a/saleor/dashboard/product/views.py
+++ b/saleor/dashboard/product/views.py
@@ -178,6 +178,7 @@ def product_detail(request, pk):
     return TemplateResponse(request, 'dashboard/product/detail.html', ctx)
 
 
+@require_POST
 @staff_member_required
 @permission_required('product.edit_product')
 def product_toggle_is_published(request, pk):
diff --git a/saleor/static/dashboard/js/components/misc.js b/saleor/static/dashboard/js/components/misc.js
index 8b9a379cc9f..f42a241cf63 100644
--- a/saleor/static/dashboard/js/components/misc.js
+++ b/saleor/static/dashboard/js/components/misc.js
@@ -20,11 +20,16 @@ export default $(document).ready((e) => {
   });
 
   $('#product-is-published').on('click', (e) => {
-    const input = $(e.currentTarget).find('input')[0];
+    const form = $(e.currentTarget).closest('form');
+    const input = form.find('input')[0];
     if (e.target === input) {
-      const url = $(e.currentTarget).attr('data-url');
+      const url = form.attr('action');
       fetch(url, {
-        credentials: 'same-origin'
+        method: 'POST',
+        credentials: 'same-origin',
+        headers: {
+          'X-CSRFToken': $('[name=csrfmiddlewaretoken]').val()
+        }
       }).then((r) => {
         return r.json();
       }).then((r) => {
diff --git a/templates/dashboard/product/detail.html b/templates/dashboard/product/detail.html
index 4a65eeecb61..761083f285d 100644
--- a/templates/dashboard/product/detail.html
+++ b/templates/dashboard/product/detail.html
@@ -162,19 +162,22 @@
           <div class="col s12">
             <div class="card">
               <div class="card-content">
-                <div class="switch" id="product-is-published" data-url="{% url 'dashboard:product-publish' product.pk %}">
-                  <label>
-                    <input type="checkbox" {% if product.is_published %} checked {% endif %}>
-                    <span class="lever"></span>
-                    <span class="label">
-                      {% if product.is_published %}
-                        {% trans "Published" context "Product field" %}
-                      {% else %}
-                        {% trans "Draft" context "Product field" %}
-                      {% endif %}
-                    </span>
-                  </label>
-                </div>
+                <form action="{% url 'dashboard:product-publish' product.pk %}">
+                  <div class="switch" id="product-is-published">
+                    <label>
+                      <input type="checkbox" {% if product.is_published %} checked {% endif %}>
+                      <span class="lever"></span>
+                      <span class="label">
+                        {% if product.is_published %}
+                          {% trans "Published" context "Product field" %}
+                        {% else %}
+                          {% trans "Draft" context "Product field" %}
+                        {% endif %}
+                      </span>
+                    </label>
+                  </div>
+                {% csrf_token %}
+                </form>
                 <h5>{% render_availability_status product %}</h5>
               </div>
             </div>


Code review comments:
diff_hunk: @@ -20,11 +20,16 @@ export default $(document).ready((e) => {
   });
 
   $('#product-is-published').on('click', (e) => {
-    const input = $(e.currentTarget).find('input')[0];
+    const form = $(e.currentTarget).closest('form');
path: saleor/static/dashboard/js/components/misc.js
text: @user1:
Could we use `id` attribute as a selector for this form?

diff_hunk: @@ -20,11 +20,16 @@ export default $(document).ready((e) => {
   });
 
   $('#product-is-published').on('click', (e) => {
-    const input = $(e.currentTarget).find('input')[0];
+    const form = $(e.currentTarget).closest('form');
+    const input = form.find('input')[0];
path: saleor/static/dashboard/js/components/misc.js
text: @user1:
I think that here also it would be safer to use the `id`.