Repository: learningequality/studio
Issue: Ricecooker tagging: Adding tags via ricecooker is broken

## Observed behavior
Adding tags via ricecooker uses an unsupported API https://github.com/learningequality/studio/blob/unstable/contentcuration/contentcuration/views/internal.py#L645

and does not work

## Expected behavior
Should use `node.tags.set(tags)` instead.

## Errors and logs
```
WARNING  WARNING: The following nodes have one or more descendants that could not be created:
WARNING  	Foraging Food (TopicNode): 1 descendant (Direct assignment to the forward side of a many-to-many set is prohibited. Use tags.set() instead.)
```


Changed files:
contentcuration/contentcuration/tests/test_chef_pipeline.py, contentcuration/contentcuration/views/internal.py

diff --git a/contentcuration/contentcuration/tests/test_chef_pipeline.py b/contentcuration/contentcuration/tests/test_chef_pipeline.py
index 27637c49e..9e9351277 100644
--- a/contentcuration/contentcuration/tests/test_chef_pipeline.py
+++ b/contentcuration/contentcuration/tests/test_chef_pipeline.py
@@ -88,7 +88,7 @@ class ChefTestCase(BaseAPITestCase):
         response = self.post(self.authenticate_user_internal_url, None)
         assert response.status_code == 200
         data = json.loads(response.content)
-        assert data["success"] == True
+        assert data["success"]
         assert data["username"] == user().email
 
     def test_check_version_bad_request(self):
@@ -184,6 +184,32 @@ class ChefTestCase(BaseAPITestCase):
             response.content
         )
 
+    def test_add_node_with_tags(self):
+        response = self.post(
+            self.create_channel_url, {"channel_data": channel_metadata}
+        )
+        assert response.status_code == 200
+        data = json.loads(response.content)
+        assert "root" in data
+
+        node_data = node_json(
+            {"kind": "video", "license": cc.License.objects.all()[0].license_name}
+        )
+        unique_title = "This is a title that we can almost certainly find uniquely later"
+        node_data["tags"] = ["test"]
+        node_data["title"] = unique_title
+        response = self.post(
+            self.add_nodes_url, {"root_id": data["root"], "content_data": [node_data]}
+        )
+        assert response.status_code == 200, "Call failed:\\n output: {}".format(
+            response.content
+        )
+
+        node = cc.ContentNode.objects.get(title=unique_title)
+
+        self.assertEqual(node.tags.count(), 1)
+        self.assertEqual(node.tags.first().tag_name, "test")
+
     def test_finish_channel_bad_request(self):
         response = self.post(self.finish_channel_url, {})
         assert response.status_code == 400
diff --git a/contentcuration/contentcuration/views/internal.py b/contentcuration/contentcuration/views/internal.py
index 8eb625f81..54097c5d0 100644
--- a/contentcuration/contentcuration/views/internal.py
+++ b/contentcuration/contentcuration/views/internal.py
@@ -219,7 +219,7 @@ def api_commit_channel(request):
         event = generate_update_event(
             channel_id,
             CHANNEL,
-            {"root_id": obj.main_tree.id, "staging_root_id": obj.staging_tree.id,},
+            {"root_id": obj.main_tree.id, "staging_root_id": obj.staging_tree.id},
         )
 
         # Mark old staging tree for garbage collection
@@ -441,7 +441,7 @@ def get_channel_status_bulk(request):
             raise PermissionDenied()
         statuses = {cid: get_status(cid) for cid in data['channel_ids']}
 
-        return Response({"success": True, "statuses": statuses,})
+        return Response({"success": True, "statuses": statuses})
     except (Channel.DoesNotExist, PermissionDenied):
         return HttpResponseNotFound(
             "No complete set of channels matching: {}".format(",".join(channel_ids))
@@ -642,8 +642,7 @@ def create_node(node_data, parent_node, sort_order):  # noqa: C901
                 )
 
     if len(tags) > 0:
-        node.tags = tags
-        node.save()
+        node.tags.set(tags)
 
     return node