Repository: elastic/apm-agent-python

Issue:
Add support for Django 3.2
Django 3.2 is slated for a release in April. Running the test suite, a few problems came up:

- [ ] App label needs to be a valid Python identifier, ours is not (renaming it from `elasticapm.contrib.django` to `elasticapm` should suffice)

Several test failures:

- [ ] `test_broken_500_handler_with_middleware`
- [ ] `test_404_middleware`
- [ ] `test_response_error_id_middleware`
- [ ] `test_django_logging_request_kwarg`
- [ ] `test_django_logging_middleware`
- [ ] `test_capture_body_config_is_dynamic_for_transactions`
- [ ] `test_capture_headers_config_is_dynamic_for_transactions`
- [ ] `test_capture_headers`
- [ ] `test_transaction_name_from_route`

Most of these look similar in nature, I suspect an issue with middlewares. Nothing jumps out in the [release notes](https://docs.djangoproject.com/en/3.2/releases/3.2/), though.



Discussion:

Resolving patch:
diff --git a/elasticapm/contrib/django/apps.py b/elasticapm/contrib/django/apps.py
--- a/elasticapm/contrib/django/apps.py
+++ b/elasticapm/contrib/django/apps.py
@@ -54,7 +54,7 @@
 
 class ElasticAPMConfig(AppConfig):
     name = "elasticapm.contrib.django"
-    label = "elasticapm.contrib.django"
+    label = "elasticapm"
     verbose_name = "ElasticAPM"
 
     def __init__(self, *args, **kwargs):


Test patch:
diff --git a/tests/contrib/django/fixtures.py b/tests/contrib/django/fixtures.py
--- a/tests/contrib/django/fixtures.py
+++ b/tests/contrib/django/fixtures.py
@@ -57,7 +57,7 @@ def django_elasticapm_client(request):
     client_config.setdefault("service_name", "app")
     client_config.setdefault("secret_token", "secret")
     client_config.setdefault("span_frames_min_duration", -1)
-    app = apps.get_app_config("elasticapm.contrib.django")
+    app = apps.get_app_config("elasticapm")
     old_client = app.client
     client = TempStoreClient(**client_config)
     register_handlers(client)
@@ -83,7 +83,7 @@ def django_sending_elasticapm_client(request, validating_httpserver):
     client_config.setdefault("secret_token", "secret")
     client_config.setdefault("transport_class", "elasticapm.transport.http.Transport")
     client_config.setdefault("span_frames_min_duration", -1)
-    app = apps.get_app_config("elasticapm.contrib.django")
+    app = apps.get_app_config("elasticapm")
     old_client = app.client
     client = DjangoClient(**client_config)
     register_handlers(client)
diff --git a/tests/contrib/django/testapp/urls.py b/tests/contrib/django/testapp/urls.py
--- a/tests/contrib/django/testapp/urls.py
+++ b/tests/contrib/django/testapp/urls.py
@@ -32,11 +32,16 @@
 
 import django
 from django.conf import settings
-from django.conf.urls import url
 from django.http import HttpResponse
 
 from tests.contrib.django.testapp import views
 
+try:
+    from django.conf.urls import re_path
+except ImportError:
+    # Django < 2
+    from django.conf.urls import url as re_path
+
 
 def handler500(request):
     if getattr(settings, "BREAK_THAT_500", False):
@@ -45,27 +50,27 @@ def handler500(request):
 
 
 urlpatterns = (
-    url(r"^render-heavy-template$", views.render_template_view, name="render-heavy-template"),
-    url(r"^render-user-template$", views.render_user_view, name="render-user-template"),
-    url(r"^no-error$", views.no_error, name="elasticapm-no-error"),
-    url(r"^no-error-slash/$", views.no_error, name="elasticapm-no-error-slash"),
-    url(r"^http-error/(?P<status>[0-9]{3})$", views.http_error, name="elasticapm-http-error"),
-    url(r"^logging$", views.logging_view, name="elasticapm-logging"),
-    url(r"^ignored-exception/$", views.ignored_exception, name="elasticapm-ignored-exception"),
-    url(r"^fake-login$", views.fake_login, name="elasticapm-fake-login"),
-    url(r"^trigger-500$", views.raise_exc, name="elasticapm-raise-exc"),
-    url(r"^trigger-500-ioerror$", views.raise_ioerror, name="elasticapm-raise-ioerror"),
-    url(r"^trigger-500-decorated$", views.decorated_raise_exc, name="elasticapm-raise-exc-decor"),
-    url(r"^trigger-500-django$", views.django_exc, name="elasticapm-django-exc"),
-    url(r"^trigger-500-template$", views.template_exc, name="elasticapm-template-exc"),
-    url(r"^trigger-500-log-request$", views.logging_request_exc, name="elasticapm-log-request-exc"),
-    url(r"^streaming$", views.streaming_view, name="elasticapm-streaming-view"),
-    url(r"^name-override$", views.override_transaction_name_view, name="elasticapm-name-override"),
+    re_path(r"^render-heavy-template$", views.render_template_view, name="render-heavy-template"),
+    re_path(r"^render-user-template$", views.render_user_view, name="render-user-template"),
+    re_path(r"^no-error$", views.no_error, name="elasticapm-no-error"),
+    re_path(r"^no-error-slash/$", views.no_error, name="elasticapm-no-error-slash"),
+    re_path(r"^http-error/(?P<status>[0-9]{3})$", views.http_error, name="elasticapm-http-error"),
+    re_path(r"^logging$", views.logging_view, name="elasticapm-logging"),
+    re_path(r"^ignored-exception/$", views.ignored_exception, name="elasticapm-ignored-exception"),
+    re_path(r"^fake-login$", views.fake_login, name="elasticapm-fake-login"),
+    re_path(r"^trigger-500$", views.raise_exc, name="elasticapm-raise-exc"),
+    re_path(r"^trigger-500-ioerror$", views.raise_ioerror, name="elasticapm-raise-ioerror"),
+    re_path(r"^trigger-500-decorated$", views.decorated_raise_exc, name="elasticapm-raise-exc-decor"),
+    re_path(r"^trigger-500-django$", views.django_exc, name="elasticapm-django-exc"),
+    re_path(r"^trigger-500-template$", views.template_exc, name="elasticapm-template-exc"),
+    re_path(r"^trigger-500-log-request$", views.logging_request_exc, name="elasticapm-log-request-exc"),
+    re_path(r"^streaming$", views.streaming_view, name="elasticapm-streaming-view"),
+    re_path(r"^name-override$", views.override_transaction_name_view, name="elasticapm-name-override"),
 )
 
 
 if django.VERSION >= (1, 8):
-    urlpatterns += (url(r"^render-jinja2-template$", views.render_jinja2_template, name="render-jinja2-template"),)
+    urlpatterns += (re_path(r"^render-jinja2-template$", views.render_jinja2_template, name="render-jinja2-template"),)
 
 if django.VERSION >= (2, 2):
     from django.urls import path
diff --git a/tests/requirements/reqs-django-3.2.txt b/tests/requirements/reqs-django-3.2.txt
new file mode 100644
--- /dev/null
+++ b/tests/requirements/reqs-django-3.2.txt
@@ -0,0 +1,2 @@
+Django>=3.2b1,<3.3
+-r reqs-base.txt