Repository: digitalfabrik/integreat-cms

Issue:
`AttributeError` on unchecked link list
### Describe the Bug
<!-- A clear and concise description of what the bug is. -->
Unchecked links do not have a `status_message`, which causes an `AttributeError` on the unchecked link list

### Steps to Reproduce

1. Add unchecked links
2. Go to `/analytics/linkcheck/unchecked/`
3. See error

### Expected Behavior
<!-- A clear and concise description of what you expected to happen. -->
The list of unchecked links should be shownn

### Actual Behavior
<!-- A clear and concise description of what actually happened. -->
`AttributeError: 'NoneType' object has no attribute 'startswith'`

### Additional Information
<!-- Add any other context (e.g. logs, screenshots, etc.) about the problem here. -->
<details>
  <summary>Traceback</summary>
  
  
  ```
Traceback (most recent call last):
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/core/handlers/exception.py", lin
e 47, in inner
    response = get_response(request)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 204
, in _get_response
    response = response.render()
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/response.py", line 105,
 in render
    self.content = self.rendered_content
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/response.py", line 83,
in rendered_content
    return template.render(context, self._request)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/backends/django.py", li
ne 61, in render
    return self.template.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 170, in
render
    return self._render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 162, in
_render
    return self.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in
render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in
render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 1
50, in render
    return compiled_parent._render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 162, in
_render
    return self.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in
render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in
render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 1
50, in render
    return compiled_parent._render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 162, in
_render
    return self.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in
render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in
render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/defaulttags.py", line 214, in render
    nodelist.append(node.render_annotated(context))
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/loader_tags.py", line 195, in render
    return template.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 172, in render
    return self._render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 162, in _render
    return self.nodelist.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 938, in render
    bit = node.render_annotated(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated
    return self.render(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 988, in render
    output = self.filter_expression.resolve(context)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/django/template/base.py", line 698, in resolve
    new_obj = func(obj, *arg_vals)
  File "/opt/integreat-cms/.venv/lib/python3.8/site-packages/integreat_cms/cms/templatetags/text_filters.py", line 34, in linkcheck_status_filter
    if status_message.startswith("Other Error:"):
AttributeError: 'NoneType' object has no attribute 'startswith'
  ```
  
</details>



Discussion:

Resolving patch:
diff --git a/integreat_cms/cms/templatetags/text_filters.py b/integreat_cms/cms/templatetags/text_filters.py
--- a/integreat_cms/cms/templatetags/text_filters.py
+++ b/integreat_cms/cms/templatetags/text_filters.py
@@ -2,6 +2,7 @@
 This is a collection of tags and filters for strings.
 """
 from django import template
+from django.utils.translation import ugettext as _
 
 register = template.Library()
 
@@ -31,6 +32,8 @@ def linkcheck_status_filter(status_message):
     :return: a concise message
     :rtype: str
     """
+    if not status_message:
+        return _("Unknown")
     if status_message.startswith("Other Error:"):
-        return "Other Error"
+        return _("Error")
     return status_message


Test patch:
diff --git a/integreat_cms/cms/fixtures/test_data.json b/integreat_cms/cms/fixtures/test_data.json
--- a/integreat_cms/cms/fixtures/test_data.json
+++ b/integreat_cms/cms/fixtures/test_data.json
@@ -1464,7 +1464,7 @@
       "title": "Alltag",
       "slug": "alltag",
       "status": "PUBLIC",
-      "content": "",
+      "content": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
       "language": 1,
       "currently_in_translation": false,
       "version": 1,
@@ -2263,5 +2263,101 @@
       "last_updated": "2020-01-22T12:52:56.726Z",
       "creator": 1
     }
+  },
+  {
+    "model": "linkcheck.url",
+    "pk": 1,
+    "fields": {
+      "url": "https://integreat-app.de/",
+      "last_checked": "2022-02-17T00:21:39.330Z",
+      "status": true,
+      "message": "200 OK",
+      "still_exists": true,
+      "redirect_to": ""
+    }
+  },
+  {
+    "model": "linkcheck.url",
+    "pk": 2,
+    "fields": {
+      "url": "https://integreat.app/",
+      "last_checked": null,
+      "status": null,
+      "message": null,
+      "still_exists": true,
+      "redirect_to": ""
+    }
+  },
+  {
+    "model": "linkcheck.url",
+    "pk": 3,
+    "fields": {
+      "url": "https://invalid.integreat.app/",
+      "last_checked": "2022-02-17T00:21:39.191Z",
+      "status": false,
+      "message": "Other Error: HTTPSConnectionPool(host='invalid.integreat.app', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7ff8c79466d0>: Failed to establish a new connection: [Errno -2] Name or service not known'))",
+      "still_exists": true,
+      "redirect_to": ""
+    }
+  },
+  {
+    "model": "linkcheck.url",
+    "pk": 4,
+    "fields": {
+      "url": "https://invalid2.integreat.app/",
+      "last_checked": "2022-02-17T00:21:39.191Z",
+      "status": false,
+      "message": "Other Error: HTTPSConnectionPool(host='invalid2.integreat.app', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7ff8c79466d0>: Failed to establish a new connection: [Errno -2] Name or service not known'))",
+      "still_exists": true,
+      "redirect_to": ""
+    }
+  },
+  {
+    "model": "linkcheck.link",
+    "pk": 1,
+    "fields": {
+      "content_type": 19,
+      "object_id": 61,
+      "field": "content",
+      "url": 1,
+      "text": "Gültiger Link",
+      "ignore": false
+    }
+  },
+  {
+    "model": "linkcheck.link",
+    "pk": 2,
+    "fields": {
+      "content_type": 19,
+      "object_id": 61,
+      "field": "content",
+      "url": 2,
+      "text": "Ungeprüfter Link",
+      "ignore": false
+    }
+  },
+  {
+    "model": "linkcheck.link",
+    "pk": 3,
+    "fields": {
+      "content_type": 19,
+      "object_id": 61,
+      "field": "content",
+      "url": 3,
+      "text": "Ungültiger Link",
+      "ignore": false
+    }
+  },
+  {
+    "model": "linkcheck.link",
+    "pk": 4,
+    "fields": {
+      "content_type": 19,
+      "object_id": 61,
+      "field": "content",
+      "url": 4,
+      "text": "Ignorierter Link",
+      "ignore": true
+    }
   }
 ]
diff --git a/tests/api/expected-outputs/api_augsburg_de_children_depth_2.json b/tests/api/expected-outputs/api_augsburg_de_children_depth_2.json
--- a/tests/api/expected-outputs/api_augsburg_de_children_depth_2.json
+++ b/tests/api/expected-outputs/api_augsburg_de_children_depth_2.json
@@ -401,8 +401,8 @@
         "path": "/augsburg/de/alltag/",
         "title": "Alltag",
         "modified_gmt": "2022-01-16T16:57:29.304Z",
-        "excerpt": "",
-        "content": "",
+        "excerpt": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
+        "content": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
         "parent": {
             "id": 0,
             "url": null,
diff --git a/tests/api/expected-outputs/augsburg_de_children.json b/tests/api/expected-outputs/augsburg_de_children.json
--- a/tests/api/expected-outputs/augsburg_de_children.json
+++ b/tests/api/expected-outputs/augsburg_de_children.json
@@ -107,8 +107,8 @@
         "path": "/augsburg/de/alltag/",
         "title": "Alltag",
         "modified_gmt": "2022-01-16T16:57:29.304Z",
-        "excerpt": "",
-        "content": "",
+        "excerpt": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
+        "content": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
         "parent": {
             "id": 0,
             "url": null,
diff --git a/tests/api/expected-outputs/augsburg_de_pages.json b/tests/api/expected-outputs/augsburg_de_pages.json
--- a/tests/api/expected-outputs/augsburg_de_pages.json
+++ b/tests/api/expected-outputs/augsburg_de_pages.json
@@ -469,8 +469,8 @@
         "path": "/augsburg/de/alltag/",
         "title": "Alltag",
         "modified_gmt": "2022-01-16T16:57:29.304Z",
-        "excerpt": "",
-        "content": "",
+        "excerpt": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
+        "content": "<a href=\"https://integreat-app.de/\">G&uuml;ltiger Link</a><br><a href=\"https://integreat.app\">Ungepr&uuml;fter Link</a><br><a href=\"https://invalid.integreat.app/\">Ung&uuml;ltiger Link</a><br><a href=\"https://invalid2.integreat.app/\">Ignorierter Link</a>",
         "parent": {
             "id": 0,
             "url": null,