diff --git a/ddtrace/contrib/django/patch.py b/ddtrace/contrib/django/patch.py index efb14252400..c4762a6b3ae 100644 --- a/ddtrace/contrib/django/patch.py +++ b/ddtrace/contrib/django/patch.py @@ -435,7 +435,7 @@ def traced_as_view(django, pin, func, instance, args, kwargs): except Exception: log.debug("Failed to instrument Django view %r", instance, exc_info=True) view = func(*args, **kwargs) - return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(view))) + return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(instance))) @trace_utils.with_traced_module @@ -484,7 +484,10 @@ def _patch(django): # DEV: this check will be replaced with import hooks in the future if "django.conf.urls.static" not in sys.modules: import django.conf.urls.static - trace_utils.wrap(django, "conf.urls.url", traced_urls_path(django)) + + if django.VERSION < (4, 0, 0): + trace_utils.wrap(django, "conf.urls.url", traced_urls_path(django)) + if django.VERSION >= (2, 0, 0): trace_utils.wrap(django, "urls.path", traced_urls_path(django)) trace_utils.wrap(django, "urls.re_path", traced_urls_path(django)) diff --git a/docs/index.rst b/docs/index.rst index 6e3aca14d0e..31af878be55 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,7 +70,7 @@ contacting support. +--------------------------------------------------+---------------+----------------+ | :ref:`consul` | >= 0.7 | Yes [3]_ | +--------------------------------------------------+---------------+----------------+ -| :ref:`django` | >= 1.8, <4.0 | Yes | +| :ref:`django` | >= 1.8 | Yes | +--------------------------------------------------+---------------+----------------+ | :ref:`djangorestframework ` | >= 3.4 | No | +--------------------------------------------------+---------------+----------------+ diff --git a/releasenotes/notes/django-4-support-eadd627959b9344e.yaml b/releasenotes/notes/django-4-support-eadd627959b9344e.yaml new file mode 100644 index 00000000000..e41d5b9f8d3 --- /dev/null +++ b/releasenotes/notes/django-4-support-eadd627959b9344e.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add django 4.0 support. \ No newline at end of file diff --git a/riotfile.py b/riotfile.py index 5c3255b7bad..cf6505df1a8 100644 --- a/riotfile.py +++ b/riotfile.py @@ -523,23 +523,21 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): ], }, ), - # TODO: Add support for Django 4.0 in tests - # Venv( - # pys=select_pys(min_version="3.8"), - # pkgs={ - # "django": [ - # "~=4.0.0", - # latest, - # ], - # }, - # ), + Venv( + pys=select_pys(min_version="3.8"), + pkgs={ + "django": [ + "~=4.0.0", + latest, + ], + }, + ), ], ), Venv( name="django_hosts", command="pytest {cmdargs} tests/contrib/django_hosts", pkgs={ - "django_hosts": ["~=4.0", latest], "pytest-django": [ "==3.10.0", ], @@ -548,28 +546,27 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): Venv( pys=["3.5"], pkgs={ + "django_hosts": ["~=4.0"], "django": ["~=2.2"], }, ), Venv( pys=select_pys(min_version="3.6"), pkgs={ + "django_hosts": ["~=4.0"], "django": [ "~=2.2", "~=3.2", ], }, ), - # TODO: Add support for Django 4.0 in tests - # Venv( - # pys=select_pys(min_version="3.8"), - # pkgs={ - # "django": [ - # "~=4.0", - # latest, - # ], - # }, - # ), + Venv( + pys=select_pys(min_version="3.8"), + pkgs={ + "django_hosts": ["~=5.0", latest], + "django": "~=4.0", + }, + ), ], ), Venv( @@ -608,15 +605,14 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "pytest-django": "==3.10.0", }, ), - # TODO: Add support for Django 4.0 in tests - # Venv( - # pys=select_pys(min_version="3.8"), - # pkgs={ - # "django": latest, - # "djangorestframework": ">=3.11,<3.12", - # "pytest-django": "==3.10.0", - # }, - # ), + Venv( + pys=select_pys(min_version="3.8"), + pkgs={ + "django": "~=4.0", + "djangorestframework": ["~=3.13", latest], + "pytest-django": "==3.10.0", + }, + ), ], ), Venv( diff --git a/tests/contrib/django/django_app/extra_urls.py b/tests/contrib/django/django_app/extra_urls.py index 33c2e61ae43..cab859c4962 100644 --- a/tests/contrib/django/django_app/extra_urls.py +++ b/tests/contrib/django/django_app/extra_urls.py @@ -1,11 +1,18 @@ -from django.conf.urls import url +import django from django.http import HttpResponse +# django.conf.urls.url was deprecated in django 3 and removed in django 4 +if django.VERSION < (4, 0, 0): + from django.conf.urls import url as handler +else: + from django.urls import re_path as handler + + def include_view(request): return HttpResponse(status=200) urlpatterns = [ - url("test/", include_view), + handler("test/", include_view), ] diff --git a/tests/contrib/django/django_app/settings.py b/tests/contrib/django/django_app/settings.py index cb55aa6343a..9ed1642a073 100644 --- a/tests/contrib/django/django_app/settings.py +++ b/tests/contrib/django/django_app/settings.py @@ -1,5 +1,7 @@ import os +import django + from ddtrace import tracer from tests.webclient import PingFilter @@ -30,13 +32,18 @@ }, } +django_cache = "django_redis.cache.RedisCache" +if django.VERSION >= (4, 0, 0): + django_cache = "django.core.cache.backends.redis.RedisCache" + + CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "unique-snowflake", }, "redis": { - "BACKEND": "django_redis.cache.RedisCache", + "BACKEND": django_cache, "LOCATION": "redis://127.0.0.1:6379/1", }, "pylibmc": { diff --git a/tests/contrib/django/django_app/urls.py b/tests/contrib/django/django_app/urls.py index c554ef47b99..3eb6e0483fa 100644 --- a/tests/contrib/django/django_app/urls.py +++ b/tests/contrib/django/django_app/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import url +import django from django.contrib.auth import login from django.contrib.auth.models import User from django.http import HttpResponse @@ -13,6 +13,13 @@ from .. import views +# django.conf.urls.url was deprecated in django 3 and removed in django 4 +if django.VERSION < (4, 0, 0): + from django.conf.urls import url as handler +else: + from django.urls import re_path as handler + + def repath_view(request): return HttpResponse(status=200) @@ -41,32 +48,32 @@ def shutdown(request): urlpatterns = [ - url(r"^$", views.index), - url(r"^simple/$", views.BasicView.as_view()), - url(r"^users/$", views.UserList.as_view(), name="users-list"), - url(r"^cached-template/$", views.TemplateCachedUserList.as_view(), name="cached-template-list"), - url(r"^safe-template/$", views.SafeTemplateUserList.as_view(), name="safe-template-list"), - url(r"^cached-users/$", cache_page(60)(views.UserList.as_view()), name="cached-users-list"), - url(r"^fail-view/$", views.ForbiddenView.as_view(), name="forbidden-view"), - url(r"^authenticated/$", authenticated_view, name="authenticated-view"), - url(r"^static-method-view/$", views.StaticMethodView.as_view(), name="static-method-view"), - url(r"^fn-view/$", views.function_view, name="fn-view"), - url(r"^feed-view/$", views.FeedView(), name="feed-view"), - url(r"^partial-view/$", views.partial_view, name="partial-view"), - url(r"^lambda-view/$", views.lambda_view, name="lambda-view"), - url(r"^error-500/$", views.error_500, name="error-500"), - url(r"^template-view/$", views.template_view, name="template-view"), - url(r"^template-simple-view/$", views.template_simple_view, name="template-simple-view"), - url(r"^template-list-view/$", views.template_list_view, name="template-list-view"), + handler(r"^$", views.index), + handler(r"^simple/$", views.BasicView.as_view()), + handler(r"^users/$", views.UserList.as_view(), name="users-list"), + handler(r"^cached-template/$", views.TemplateCachedUserList.as_view(), name="cached-template-list"), + handler(r"^safe-template/$", views.SafeTemplateUserList.as_view(), name="safe-template-list"), + handler(r"^cached-users/$", cache_page(60)(views.UserList.as_view()), name="cached-users-list"), + handler(r"^fail-view/$", views.ForbiddenView.as_view(), name="forbidden-view"), + handler(r"^authenticated/$", authenticated_view, name="authenticated-view"), + handler(r"^static-method-view/$", views.StaticMethodView.as_view(), name="static-method-view"), + handler(r"^fn-view/$", views.function_view, name="fn-view"), + handler(r"^feed-view/$", views.FeedView(), name="feed-view"), + handler(r"^partial-view/$", views.partial_view, name="partial-view"), + handler(r"^lambda-view/$", views.lambda_view, name="lambda-view"), + handler(r"^error-500/$", views.error_500, name="error-500"), + handler(r"^template-view/$", views.template_view, name="template-view"), + handler(r"^template-simple-view/$", views.template_simple_view, name="template-simple-view"), + handler(r"^template-list-view/$", views.template_list_view, name="template-list-view"), re_path(r"re-path.*/", repath_view), path("path/", path_view), path("include/", include("tests.contrib.django.django_app.extra_urls")), # This must precede composed-view. - url(r"^some-static-view/$", TemplateView.as_view(template_name="my-template.html")), - url(r"^composed-template-view/$", views.ComposedTemplateView.as_view(), name="composed-template-view"), - url(r"^composed-get-view/$", views.ComposedGetView.as_view(), name="composed-get-view"), - url(r"^composed-view/$", views.ComposedView.as_view(), name="composed-view"), - url(r"^404-view/$", views.not_found_view, name="404-view"), - url(r"^shutdown-tracer/$", shutdown, name="shutdown-tracer"), - url(r"^alter-resource/$", views.alter_resource), + handler(r"^some-static-view/$", TemplateView.as_view(template_name="my-template.html")), + handler(r"^composed-template-view/$", views.ComposedTemplateView.as_view(), name="composed-template-view"), + handler(r"^composed-get-view/$", views.ComposedGetView.as_view(), name="composed-get-view"), + handler(r"^composed-view/$", views.ComposedView.as_view(), name="composed-view"), + handler(r"^404-view/$", views.not_found_view, name="404-view"), + handler(r"^shutdown-tracer/$", shutdown, name="shutdown-tracer"), + handler(r"^alter-resource/$", views.alter_resource), ] diff --git a/tests/contrib/django_hosts/django_app/app_urls.py b/tests/contrib/django_hosts/django_app/app_urls.py index 9ee9f7bc553..6afa83c6f44 100644 --- a/tests/contrib/django_hosts/django_app/app_urls.py +++ b/tests/contrib/django_hosts/django_app/app_urls.py @@ -1,9 +1,15 @@ -from django.conf.urls import url +import django from .. import views +if django.VERSION < (4, 0, 0): + from django.conf.urls import url as handler +else: + from django.urls import re_path as handler + + urlpatterns = [ - url(r"^$", views.index), - url(r"^simple/$", views.BasicView.as_view()), + handler(r"^$", views.index), + handler(r"^simple/$", views.BasicView.as_view()), ] diff --git a/tests/contrib/django_hosts/django_app/extra_urls.py b/tests/contrib/django_hosts/django_app/extra_urls.py index 33c2e61ae43..e753d36f5e5 100644 --- a/tests/contrib/django_hosts/django_app/extra_urls.py +++ b/tests/contrib/django_hosts/django_app/extra_urls.py @@ -1,11 +1,17 @@ -from django.conf.urls import url +import django from django.http import HttpResponse +if django.VERSION < (4, 0, 0): + from django.conf.urls import url as handler +else: + from django.urls import re_path as handler + + def include_view(request): return HttpResponse(status=200) urlpatterns = [ - url("test/", include_view), + handler("test/", include_view), ] diff --git a/tests/contrib/djangorestframework/app/views.py b/tests/contrib/djangorestframework/app/views.py index bba93592b75..390e90a0aaa 100644 --- a/tests/contrib/djangorestframework/app/views.py +++ b/tests/contrib/djangorestframework/app/views.py @@ -1,11 +1,18 @@ +import django from django.conf.urls import include -from django.conf.urls import url from django.contrib.auth.models import User from rest_framework import routers from rest_framework import serializers from rest_framework import viewsets +# django.conf.urls.url was deprecated in django 3 and removed in django 4 +if django.VERSION < (4, 0, 0): + from django.conf.urls import url as handler +else: + from django.urls import re_path as handler + + class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User @@ -27,6 +34,6 @@ class UserViewSet(viewsets.ModelViewSet): # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ - url(r"^", include(router.urls)), - url(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")), + handler(r"^", include(router.urls)), + handler(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")), ]