Repository: saleor/saleor

Issue:
Unable to switch menu item from category to collection link
### What I'm trying to achieve
Edit a menu item pointing to a category, to a collection.

### Steps to reproduce the problem

1. Go to a menu (navbar or footer, doesn't matter);
2. Add a menu item pointing to a category and save;
3. Edit this menu to be pointing to a collection. If the bug worked, you should see that it is still pointing to a category.

### What I expected to happen
Having the menu item to be pointed to the passed collection.

### What happened instead/how it failed
The menu item is pointed to the previously selected category.


Discussion:

Resolving patch:
diff --git a/saleor/dashboard/menu/forms.py b/saleor/dashboard/menu/forms.py
--- a/saleor/dashboard/menu/forms.py
+++ b/saleor/dashboard/menu/forms.py
@@ -8,6 +8,7 @@
 from ...site.models import SiteSettings
 from ..forms import (
     AjaxSelect2CombinedChoiceField, OrderedModelMultipleChoiceField)
+from .utils import update_menu_item_linked_object
 
 
 class AssignMenuForm(forms.ModelForm):
@@ -93,17 +94,9 @@ def clean(self):
                 code='invalid')
         return self.cleaned_data
 
-    def save(self, commit=True):
+    def save(self):
         linked_object = self.cleaned_data.get('linked_object')
-
-        if isinstance(linked_object, Collection):
-            self.instance.collection = linked_object
-        elif isinstance(linked_object, Category):
-            self.instance.category = linked_object
-        elif isinstance(linked_object, Page):
-            self.instance.page = linked_object
-
-        return super().save(commit)
+        return update_menu_item_linked_object(self.instance, linked_object)
 
 
 class ReorderMenuItemsForm(forms.ModelForm):
diff --git a/saleor/dashboard/menu/utils.py b/saleor/dashboard/menu/utils.py
new file mode 100644
--- /dev/null
+++ b/saleor/dashboard/menu/utils.py
@@ -0,0 +1,18 @@
+from ...page.models import Page
+from ...product.models import Category, Collection
+
+
+def update_menu_item_linked_object(menu_item, linked_object):
+    """Assign new linked object to a menu item. Clear other links."""
+    menu_item.category = None
+    menu_item.collection = None
+    menu_item.page = None
+
+    if isinstance(linked_object, Category):
+        menu_item.category = linked_object
+    elif isinstance(linked_object, Collection):
+        menu_item.collection = linked_object
+    elif isinstance(linked_object, Page):
+        menu_item.page = linked_object
+
+    return menu_item.save()
diff --git a/saleor/menu/models.py b/saleor/menu/models.py
--- a/saleor/menu/models.py
+++ b/saleor/menu/models.py
@@ -66,12 +66,12 @@ def destination_display(self):
             prefix = pgettext_lazy('Link object type description', 'URL: ')
             return prefix + self.url
 
-        if isinstance(linked_object, Collection):
-            prefix = pgettext_lazy(
-                'Link object type description', 'Collection: ')
-        elif isinstance(linked_object, Category):
+        if isinstance(linked_object, Category):
             prefix = pgettext_lazy(
                 'Link object type description', 'Category: ')
+        elif isinstance(linked_object, Collection):
+            prefix = pgettext_lazy(
+                'Link object type description', 'Collection: ')
         else:
             prefix = pgettext_lazy(
                 'Link object type description', 'Page: ')


Test patch:
diff --git a/tests/dashboard/test_menu.py b/tests/dashboard/test_menu.py
--- a/tests/dashboard/test_menu.py
+++ b/tests/dashboard/test_menu.py
@@ -1,10 +1,10 @@
 import json
 
 import pytest
-
 from django.urls import reverse
-from saleor.account.models import User
+
 from saleor.dashboard.menu.forms import AssignMenuForm
+from saleor.dashboard.menu.utils import update_menu_item_linked_object
 from saleor.menu.models import Menu, MenuItem
 
 from ..utils import get_redirect_location
@@ -304,3 +304,14 @@ def test_view_ajax_menu_links(
 
     assert response.status_code == 200
     assert resp_decoded == {'results': groups}
+
+
+def test_update_menu_item_linked_object(menu, default_category, page):
+    menu_item = menu.items.create(category=default_category)
+
+    update_menu_item_linked_object(menu_item, page)
+
+    assert menu_item.linked_object == page
+    assert menu_item.get_url() == page.get_absolute_url()
+    assert not menu_item.category
+    assert not menu_item.collection