Repository: getsentry/sentry-python

Issue:
Django template span has invalid description
### Environment

How do you use Sentry?
Self-hosted, version 21.2.0

Which SDK and version?
Python 1.0.0

### Steps to Reproduce

1. Set up performance tracing for a Django application
2. Send performance data for a view which is a subclass of `TemplateView`
3. View the performance information for the view in Sentry

### Expected Result

Sentry accepts all of the submitted performance data.

### Actual Result

The following alert appears above the performance information:

<img width="1304" alt="Sentry Error" src="https://user-images.githubusercontent.com/50265209/110369051-d15bf580-8017-11eb-80c4-b983385d9a21.png">

This is caused by #957.

At https://github.com/Christophe31/sentry-python/blob/db2d87e503b0178a7d56bdedb00db19ac6ddc9d8/sentry_sdk/integrations/django/templates.py#L60, a span is created with the description `self.template_name`. If the `TemplateResponse` was created by a Django `TemplateView`, `template_name` will be a list, not a string.

A similar error can occur with `django.shortcuts.render`, but I don't use it, so I haven't experienced the issue there.



Discussion:

Resolving patch:
diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py
--- a/sentry_sdk/integrations/django/templates.py
+++ b/sentry_sdk/integrations/django/templates.py
@@ -42,6 +42,15 @@ def get_template_frame_from_exception(exc_value):
     return None
 
 
+def _get_template_name_description(template_name):
+    # type: (str) -> str
+    if isinstance(template_name, (list, tuple)):
+        if template_name:
+            return "[{}, ...]".format(template_name[0])
+    else:
+        return template_name
+
+
 def patch_templates():
     # type: () -> None
     from django.template.response import SimpleTemplateResponse
@@ -57,7 +66,8 @@ def rendered_content(self):
             return real_rendered_content.fget(self)
 
         with hub.start_span(
-            op="django.template.render", description=self.template_name
+            op="django.template.render",
+            description=_get_template_name_description(self.template_name),
         ) as span:
             span.set_data("context", self.context_data)
             return real_rendered_content.fget(self)
@@ -78,7 +88,8 @@ def render(request, template_name, context=None, *args, **kwargs):
             return real_render(request, template_name, context, *args, **kwargs)
 
         with hub.start_span(
-            op="django.template.render", description=template_name
+            op="django.template.render",
+            description=_get_template_name_description(template_name),
         ) as span:
             span.set_data("context", context)
             return real_render(request, template_name, context, *args, **kwargs)


Test patch:
diff --git a/tests/integrations/django/myapp/views.py b/tests/integrations/django/myapp/views.py
--- a/tests/integrations/django/myapp/views.py
+++ b/tests/integrations/django/myapp/views.py
@@ -122,7 +122,9 @@ def template_test(request, *args, **kwargs):
 
 @csrf_exempt
 def template_test2(request, *args, **kwargs):
-    return TemplateResponse(request, "user_name.html", {"user_age": 25})
+    return TemplateResponse(
+        request, ("user_name.html", "another_template.html"), {"user_age": 25}
+    )
 
 
 @csrf_exempt
diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py
--- a/tests/integrations/django/test_basic.py
+++ b/tests/integrations/django/test_basic.py
@@ -563,18 +563,25 @@ def test_render_spans(sentry_init, client, capture_events, render_span_tree):
         integrations=[DjangoIntegration()],
         traces_sample_rate=1.0,
     )
-    views_urls = [reverse("template_test2")]
+    views_tests = [
+        (
+            reverse("template_test2"),
+            '- op="django.template.render": description="[user_name.html, ...]"',
+        ),
+    ]
     if DJANGO_VERSION >= (1, 7):
-        views_urls.append(reverse("template_test"))
+        views_tests.append(
+            (
+                reverse("template_test"),
+                '- op="django.template.render": description="user_name.html"',
+            ),
+        )
 
-    for url in views_urls:
+    for url, expected_line in views_tests:
         events = capture_events()
         _content, status, _headers = client.get(url)
         transaction = events[0]
-        assert (
-            '- op="django.template.render": description="user_name.html"'
-            in render_span_tree(transaction)
-        )
+        assert expected_line in render_span_tree(transaction)
 
 
 def test_middleware_spans(sentry_init, client, capture_events, render_span_tree):