diff --git a/newrelic/config.py b/newrelic/config.py index eae872413b..04978c97e3 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -2929,6 +2929,10 @@ def _process_module_builtin_defaults(): _process_module_definition("flask_restplus.api", "newrelic.hooks.component_flask_rest", "instrument_flask_rest") _process_module_definition("flask_restx.api", "newrelic.hooks.component_flask_rest", "instrument_flask_rest") + _process_module_definition( + "wagtail.models.pages", "newrelic.hooks.framework_wagtail", "instrument_wagtail_models_pages" + ) + _process_module_definition("graphql_server", "newrelic.hooks.component_graphqlserver", "instrument_graphqlserver") _process_module_definition( diff --git a/newrelic/hooks/framework_django.py b/newrelic/hooks/framework_django.py index 607ad26623..76549f91cc 100644 --- a/newrelic/hooks/framework_django.py +++ b/newrelic/hooks/framework_django.py @@ -484,7 +484,21 @@ def wrapper(wrapped, instance, args, kwargs): if transaction is None: return wrapped(*args, **kwargs) - transaction.set_transaction_name(name, priority=priority) + # Wagtail is built on top of Django. It uses metaclasses where the route method + # is located on the base class which results in transaction having the same + # name. Use the child class instead of the base class name. Set the priority=6 + # to override the priority set in other parts of this hook file so that the + # more explicit name takes precedence. + new_name = name + try: + from wagtail.models.pages import Page + except: + Page = None + if instance and isinstance(instance, Page): + new_name = f"{callable_name(instance)}.{wrapped.__name__}" + transaction.set_transaction_name(new_name, priority=6) + else: + transaction.set_transaction_name(new_name, priority=priority) with FunctionTrace(name=name, source=wrapped): try: return wrapped(*args, **kwargs) diff --git a/newrelic/hooks/framework_wagtail.py b/newrelic/hooks/framework_wagtail.py new file mode 100644 index 0000000000..34e202864f --- /dev/null +++ b/newrelic/hooks/framework_wagtail.py @@ -0,0 +1,37 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from newrelic.api.transaction import current_transaction +from newrelic.common.object_names import callable_name +from newrelic.common.object_wrapper import wrap_function_wrapper + + +def _nr_wrapper_route_for_request(wrapped, instance, args, kwargs): + transaction = current_transaction() + + if not transaction: + return wrapped(*args, **kwargs) + + route_result = wrapped(*args, **kwargs) + if route_result: + page, args, kwargs = route_result + name = callable_name(page.route) + trans.set_transaction_name(name, priority=6) + + return route_result + + +def instrument_wagtail_models_pages(module): + if hasattr(module, "Page"): + if hasattr(module.Page, "route_for_request"): + wrap_function_wrapper(module, "Page.route_for_request", _nr_wrapper_route_for_request) diff --git a/tests/framework_wagtail/__init__.py b/tests/framework_wagtail/__init__.py new file mode 100644 index 0000000000..bfe7af1430 --- /dev/null +++ b/tests/framework_wagtail/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/tests/framework_wagtail/_target_application.py b/tests/framework_wagtail/_target_application.py new file mode 100644 index 0000000000..f80fe709f1 --- /dev/null +++ b/tests/framework_wagtail/_target_application.py @@ -0,0 +1,18 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import webtest +from wsgi import application + +_target_application = webtest.TestApp(application) diff --git a/tests/framework_wagtail/conftest.py b/tests/framework_wagtail/conftest.py new file mode 100644 index 0000000000..187ec232e0 --- /dev/null +++ b/tests/framework_wagtail/conftest.py @@ -0,0 +1,33 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from newrelic.api.application import application_instance +from newrelic.core.agent import agent_instance + + +# Even though `django_collector_agent_registration_fixture()` also +# has the application deletion during the breakdown of the fixture, +# and `django_collector_agent_registration_fixture()` is scoped to +# "function", not all modules are using this. Some are using +# `collector_agent_registration_fixture()` scoped to "module". +# Therefore, for those instances, we need to make sure that the +# application is removed from the agent. +@pytest.fixture(scope="module", autouse=True) +def remove_application_from_agent(): + yield + application = application_instance() + if application and application.name and (application.name in agent_instance()._applications): + del agent_instance()._applications[application.name] diff --git a/tests/framework_wagtail/dummy_app/__init__.py b/tests/framework_wagtail/dummy_app/__init__.py new file mode 100644 index 0000000000..bfe7af1430 --- /dev/null +++ b/tests/framework_wagtail/dummy_app/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/tests/framework_wagtail/dummy_app/apps.py b/tests/framework_wagtail/dummy_app/apps.py new file mode 100644 index 0000000000..ecb9f5a63f --- /dev/null +++ b/tests/framework_wagtail/dummy_app/apps.py @@ -0,0 +1,20 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.apps import AppConfig + + +class LibraryConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "dummy_app" diff --git a/tests/framework_wagtail/dummy_app/models.py b/tests/framework_wagtail/dummy_app/models.py new file mode 100644 index 0000000000..8409cd9f56 --- /dev/null +++ b/tests/framework_wagtail/dummy_app/models.py @@ -0,0 +1,40 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.db import models +from wagtail.models import Page +from wagtail.contrib.routable_page.models import RoutablePage, re_path +from wagtail.fields import RichTextField + +class HomePage(Page): + body = RichTextField(blank=True) + + content_panels = Page.content_panels + ["body"] + + +class RoutablePage(RoutablePage): + body = RichTextField(blank=True) + + content_panels = Page.content_panels + ["body"] + + @re_path(r"^routable") + def index(self, request): + # Handle URLs of the form / + return super().serve(request) + + class Meta: + verbose_name = "Routable page" + + def __str__(self): + return "Page from routable" diff --git a/tests/framework_wagtail/migrations/0001_initial.py b/tests/framework_wagtail/migrations/0001_initial.py new file mode 100644 index 0000000000..3d0b7bf349 --- /dev/null +++ b/tests/framework_wagtail/migrations/0001_initial.py @@ -0,0 +1,100 @@ +from django.db import migrations, models +import wagtail.fields +from wagtail.models import Page +import django.db.models.deletion +import wagtail.contrib.routable_page.models + + +def create_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + Page = apps.get_model("wagtailcore.Page") + Site = apps.get_model("wagtailcore.Site") + HomePage = apps.get_model("home.HomePage") + + # Delete the default homepage + # If migration is run multiple times, it may have already been deleted + Page.objects.filter(id=2).delete() + + # Create content type for homepage model + homepage_content_type, __ = ContentType.objects.get_or_create( + model="homepage", app_label="home" + ) + + # Create a new homepage + homepage = HomePage.objects.create( + title="Home", + draft_title="Home", + slug="home", + content_type=homepage_content_type, + path="00010001", + depth=2, + numchild=0, + url_path="/home/", + ) + + # Create a site with the new homepage set as the root + Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True) + +def remove_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + HomePage = apps.get_model("home.HomePage") + + # Delete the default homepage + # Page and Site objects CASCADE + HomePage.objects.filter(slug="home", depth=2).delete() + + # Delete content type for homepage model + ContentType.objects.filter(model="homepage", app_label="home").delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailcore", "0040_page_draft_title"), + ] + + operations = [ + migrations.CreateModel( + name="HomePage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + on_delete=models.CASCADE, + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + migrations.RunPython(create_homepage, remove_homepage), + migrations.AddField( + model_name='homepage', + name='body', + field=wagtail.fields.RichTextField(blank=True), + ), + migrations.CreateModel( + name='RoutablePage', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), + ], + options={ + 'verbose_name': 'Routable page', + }, + bases=(wagtail.contrib.routable_page.models.RoutablePage,), + ), + migrations.AddField( + model_name='routablepage', + name='body', + field=wagtail.fields.RichTextField(blank=True), + ), + ] diff --git a/tests/framework_wagtail/settings.py b/tests/framework_wagtail/settings.py new file mode 100644 index 0000000000..e11031e6dc --- /dev/null +++ b/tests/framework_wagtail/settings.py @@ -0,0 +1,70 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from pathlib import Path + +BASE_DIR = Path(__file__).parent +DEBUG = True + +# Make this unique, and don't share it with anybody. +SECRET_KEY = "cookies" + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ("django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader") + +MIDDLEWARE = ( + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.gzip.GZipMiddleware", +) + +ROOT_URLCONF = "urls" + +TEMPLATE_DIRS = [BASE_DIR / "templates"] + +# For Django 1.10 compatibility because TEMPLATE_DIRS is deprecated +TEMPLATES = [{"BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": TEMPLATE_DIRS}] + +INSTALLED_APPS = [ + "dummy_app", + "wagtail.contrib.forms", + "wagtail.contrib.redirects", + "wagtail.embeds", + "wagtail.sites", + "wagtail.users", + "wagtail.snippets", + "wagtail.documents", + "wagtail.images", + "wagtail.search", + "wagtail.admin", + "wagtail", + "django_filters", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + } +} diff --git a/tests/framework_wagtail/templates/base.html b/tests/framework_wagtail/templates/base.html new file mode 100644 index 0000000000..213ff76543 --- /dev/null +++ b/tests/framework_wagtail/templates/base.html @@ -0,0 +1,46 @@ +{% load static wagtailcore_tags wagtailuserbar %} + + + + + + + {% block title %} + {% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %} + {% endblock %} + {% block title_suffix %} + {% wagtail_site as current_site %} + {% if current_site and current_site.site_name %}- {{ current_site.site_name }}{% endif %} + {% endblock %} + + {% if page.search_description %} + + {% endif %} + + + {# Force all links in the live preview panel to be opened in a new tab #} + {% if request.in_preview_panel %} + + {% endif %} + + {# Global stylesheets #} + + + {% block extra_css %} + {# Override this in templates to add extra stylesheets #} + {% endblock %} + + + + {% wagtailuserbar %} + + {% block content %}{% endblock %} + + {# Global javascript #} + + + {% block extra_js %} + {# Override this in templates to add extra javascript #} + {% endblock %} + + diff --git a/tests/framework_wagtail/test_application.py b/tests/framework_wagtail/test_application.py new file mode 100644 index 0000000000..a9e76bd06f --- /dev/null +++ b/tests/framework_wagtail/test_application.py @@ -0,0 +1,189 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from testing_support.fixtures import ( + collector_agent_registration_fixture, + collector_available_fixture, + override_application_settings, + override_generic_settings, + override_ignore_status_codes, +) +from testing_support.validators.validate_code_level_metrics import validate_code_level_metrics +from testing_support.validators.validate_transaction_errors import validate_transaction_errors +from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics + + +_default_settings = { + "package_reporting.enabled": False, # Turn off package reporting for testing as it causes slow downs. + "transaction_tracer.explain_threshold": 0.0, + "transaction_tracer.transaction_threshold": 0.0, + "transaction_tracer.stack_trace_threshold": 0.0, + "debug.log_data_collector_payloads": True, + "debug.record_transaction_failure": True, + "debug.log_autorum_middleware": True, +} + +collector_agent_registration = collector_agent_registration_fixture( + app_name="Python Agent Test (framework_django)", default_settings=_default_settings, scope="module" +) + +@pytest.fixture +def database(): + +def create_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + Page = apps.get_model("wagtailcore.Page") + Site = apps.get_model("wagtailcore.Site") + HomePage = apps.get_model("home.HomePage") + + # Delete the default homepage + # If migration is run multiple times, it may have already been deleted + Page.objects.filter(id=2).delete() + + # Create content type for homepage model + homepage_content_type, __ = ContentType.objects.get_or_create( + model="homepage", app_label="home" + ) + + # Create a new homepage + homepage = HomePage.objects.create( + title="Home", + draft_title="Home", + slug="home", + content_type=homepage_content_type, + path="00010001", + depth=2, + numchild=0, + url_path="/home/", + ) + + # Create a site with the new homepage set as the root + Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True) + +def remove_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + HomePage = apps.get_model("home.HomePage") + + # Delete the default homepage + # Page and Site objects CASCADE + HomePage.objects.filter(slug="home", depth=2).delete() + + # Delete content type for homepage model + ContentType.objects.filter(model="homepage", app_label="home").delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailcore", "0040_page_draft_title"), + ] + + operations = [ + migrations.CreateModel( + name="HomePage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + on_delete=models.CASCADE, + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + migrations.RunPython(create_homepage, remove_homepage), + migrations.AddField( + model_name='homepage', + name='body', + field=wagtail.fields.RichTextField(blank=True), + ), + migrations.CreateModel( + name='RoutablePage', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), + ], + options={ + 'verbose_name': 'Routable page', + }, + bases=(wagtail.contrib.routable_page.models.RoutablePage,), + ), + migrations.AddField( + model_name='routablepage', + name='body', + field=wagtail.fields.RichTextField(blank=True), + ), + ] + + +def target_application(): + from _target_application import _target_application + + return _target_application + + +@validate_transaction_metrics( + "views:index", + scoped_metrics=[ + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:index", 1), + ] +) +def test_home(): + test_application = target_application() + response = test_application.get("") + + +@validate_transaction_metrics( + "views:index", + scoped_metrics=[ + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:index", 1), + ] +) +def test_routable(): + test_application = target_application() + response = test_application.get("/routable") + + +@validate_transaction_metrics( + "views:index", + scoped_metrics=[ + ("Function/django.core.handlers.wsgi:WSGIHandler.__call__", 1), + ("Python/WSGI/Application", 1), + ("Python/WSGI/Response", 1), + ("Python/WSGI/Finalize", 1), + ("Function/views:index", 1), + ] +) +def test_routable_routable(): + test_application = target_application() + response = test_application.get("/routable/routable") diff --git a/tests/framework_wagtail/urls.py b/tests/framework_wagtail/urls.py new file mode 100644 index 0000000000..91baf0b51f --- /dev/null +++ b/tests/framework_wagtail/urls.py @@ -0,0 +1,36 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from django.conf import settings +from django.urls import include, path +from django.contrib import admin + +from wagtail.admin import urls as wagtailadmin_urls +from wagtail import urls as wagtail_urls +from wagtail.documents import urls as wagtaildocs_urls + + +urlpatterns = [ + path("django-admin/", admin.site.urls), + path("admin/", include(wagtailadmin_urls)), + path("documents/", include(wagtaildocs_urls)), +] +urlpatterns = urlpatterns + [ + # For anything not caught by a more specific rule above, hand over to + # Wagtail's page serving mechanism. This should be the last pattern in + # the list: + path("", include(wagtail_urls)), + # Alternatively, if you want Wagtail pages to be served from a subpath + # of your site, rather than the site root: + # path("pages/", include(wagtail_urls)), +] diff --git a/tests/framework_wagtail/wsgi.py b/tests/framework_wagtail/wsgi.py new file mode 100644 index 0000000000..50cb53ba82 --- /dev/null +++ b/tests/framework_wagtail/wsgi.py @@ -0,0 +1,49 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +WSGI config for sample project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" + +import os + +# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks +# if running multiple sites in the same mod_wsgi process. To fix this, use +# mod_wsgi daemon mode with each site in its own daemon process, or use +# os.environ["DJANGO_SETTINGS_MODULE"] = "sample.settings" +# +# Note that DJANGO_SETTINGS_MODULE may be set in tox.ini! + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application + +application = get_wsgi_application() + + diff --git a/tox.ini b/tox.ini index 8fc1a5ece3..ddbb9276d2 100644 --- a/tox.ini +++ b/tox.ini @@ -170,6 +170,7 @@ envlist = python-framework_strawberry-{py39,py310,py311,py312,py313,py314,py314t}-strawberrylatest, python-framework_tornado-{py39,py310,py311,py312,py313,py314,py314t}-tornadolatest, ; Remove `python-framework_tornado-{py314,py314t}-tornadomaster` temporarily + python-framework_wagtail-{py39,py310,py311,py312,py313,py314,py314t}-wagtaillatest, python-framework_tornado-{py310,py311,py312,py313}-tornadomaster, python-hybridagent_ariadne-{py39,py310,py311,py312,py313,py314,py314t}, python-hybridagent_dynamodb-{py39,py310,py311,py312,py313,py314,py314t}, @@ -399,6 +400,7 @@ deps = framework_tornado: pycurl framework_tornado-tornadolatest: tornado framework_tornado-tornadomaster: https://github.com/tornadoweb/tornado/archive/master.zip + framework_wagtail-wagtaillatest: wagtail hybridagent_aiopg: opentelemetry-api hybridagent_aiopg: opentelemetry-instrumentation-aiopg hybridagent_aiopg: aiopg @@ -596,6 +598,7 @@ changedir = framework_starlette: tests/framework_starlette framework_strawberry: tests/framework_strawberry framework_tornado: tests/framework_tornado + framework_wagtail: tests/framework_wagtail hybridagent_aiopg: tests/hybridagent_aiopg hybridagent_ariadne: tests/hybridagent_ariadne hybridagent_dynamodb: tests/hybridagent_dynamodb