Repository: learningequality/kolibri

Issue:
CSV export fails if logs refer to channel that no longer exists locally
### Observed behavior

Tried to export CSV log via http://kolibribeta.learningequality.org/management/facility/#/data and got a 500 (traceback below). I think the database summary logs referred to a channel that we since deleted. We try to extract the human-readable channel names to include as a column in the CSV file, but this blows up if we don't have the channel metadata anymore.

### Expected behavior

Should have exported the CSV successfully, and just skipped the title for any channels it couldn't find, and leave those cells blank.

### User-facing consequences

Can completely block the CSV export feature from working.

### Errors and logs

```
ERROR 2017-11-30 23:09:11,954 base Internal Server Error: /api/contentsessionlogcsv/
Traceback (most recent call last):
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/django/core/handlers/base.py", line 149, in get_response
    response = self.process_exception_by_middleware(e, request)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/django/core/handlers/base.py", line 147, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/viewsets.py", line 87, in view
    return self.dispatch(request, *args, **kwargs)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/views.py", line 466, in dispatch
    response = self.handle_exception(exc)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/views.py", line 463, in dispatch
    response = handler(request, *args, **kwargs)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/mixins.py", line 48, in list
    return Response(serializer.data)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/serializers.py", line 674, in data
    ret = super(ListSerializer, self).data
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/serializers.py", line 239, in data
    self._data = self.to_representation(self.instance)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/serializers.py", line 614, in to_representation
    self.child.to_representation(item) for item in iterable
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/serializers.py", line 472, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/rest_framework/fields.py", line 1653, in to_representation
    return method(value)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/logger/csv.py", line 38, in get_content_title
    channel = ChannelMetadata.objects.get(id=obj.channel_id)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/django/db/models/manager.py", line 122, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/var/www/.pex/install/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl.3f4dacee3f158ff5e739b8a2a0b0b1cdb1b7a8e1/kolibri-0.7.0.dev020171130214228_git-py2.py3-none-any.whl/kolibri/dist/django/db/models/query.py", line 387, in get
    self.model._meta.object_name
DoesNotExist: ChannelMetadata matching query does not exist.
```

### Steps to reproduce

1. Download a channel
2. Interact with content to generate logs
3. Delete the channel
4. Try to export the CSV file

### Context

* Kolibri version: 0.7.0.dev020171130214228-git
* Operating system: Ubuntu
* Browser: Chrome



Discussion:

Resolving patch:
diff --git a/kolibri/logger/csv.py b/kolibri/logger/csv.py
--- a/kolibri/logger/csv.py
+++ b/kolibri/logger/csv.py
@@ -35,8 +35,7 @@ def get_channel_name(self, obj):
         return channel.name
 
     def get_content_title(self, obj):
-        channel = ChannelMetadata.objects.get(id=obj.channel_id)
-        node = ContentNode.objects.filter(tree_id=channel.root.tree_id).first()
+        node = ContentNode.objects.filter(content_id=obj.content_id).first()
         if node:
             return node.title
         else:


Test patch:
diff --git a/kolibri/logger/test/test_api.py b/kolibri/logger/test/test_api.py
--- a/kolibri/logger/test/test_api.py
+++ b/kolibri/logger/test/test_api.py
@@ -12,6 +12,7 @@
 from rest_framework import status
 from rest_framework.test import APITestCase
 
+from kolibri.content.models import ChannelMetadata, ContentNode
 from kolibri.core.exams.models import Exam
 from kolibri.logger.models import ExamLog
 
@@ -326,6 +327,17 @@ def test_csv_download(self):
             self.assertEqual(len(results[0]), len(row))
         self.assertEqual(len(results[1:]), expected_count)
 
+    def test_csv_download_deleted_content(self):
+        self.client.login(username=self.admin.username, password=DUMMY_PASSWORD, facility=self.facility)
+        expected_count = ContentSummaryLog.objects.count()
+        ContentNode.objects.all().delete()
+        ChannelMetadata.objects.all().delete()
+        response = self.client.get(reverse('contentsummarylogcsv-list'))
+        results = list(csv.reader(row for row in response.content.decode("utf-8").split("\n") if row))
+        for row in results[1:]:
+            self.assertEqual(len(results[0]), len(row))
+        self.assertEqual(len(results[1:]), expected_count)
+
 
 class ContentSessionLogCSVExportTestCase(APITestCase):
 
@@ -353,6 +365,17 @@ def test_csv_download(self):
             self.assertEqual(len(results[0]), len(row))
         self.assertEqual(len(results[1:]), expected_count)
 
+    def test_csv_download_deleted_content(self):
+        self.client.login(username=self.admin.username, password=DUMMY_PASSWORD, facility=self.facility)
+        expected_count = ContentSessionLog.objects.count()
+        ContentNode.objects.all().delete()
+        ChannelMetadata.objects.all().delete()
+        response = self.client.get(reverse('contentsessionlogcsv-list'))
+        results = list(csv.reader(row for row in response.content.decode("utf-8").split("\n") if row))
+        for row in results[1:]:
+            self.assertEqual(len(results[0]), len(row))
+        self.assertEqual(len(results[1:]), expected_count)
+
 
 class ExamAttemptLogAPITestCase(APITestCase):