Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
16 changes: 15 additions & 1 deletion newrelic/hooks/framework_django.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions newrelic/hooks/framework_wagtail.py
Original file line number Diff line number Diff line change
@@ -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)

Check failure on line 29 in newrelic/hooks/framework_wagtail.py

View workflow job for this annotation

GitHub Actions / MegaLinter

ruff (F821)

newrelic/hooks/framework_wagtail.py:29:9: F821 Undefined name `trans`

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)
14 changes: 14 additions & 0 deletions tests/framework_wagtail/__init__.py
Original file line number Diff line number Diff line change
@@ -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.

18 changes: 18 additions & 0 deletions tests/framework_wagtail/_target_application.py
Original file line number Diff line number Diff line change
@@ -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)
33 changes: 33 additions & 0 deletions tests/framework_wagtail/conftest.py
Original file line number Diff line number Diff line change
@@ -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]
14 changes: 14 additions & 0 deletions tests/framework_wagtail/dummy_app/__init__.py
Original file line number Diff line number Diff line change
@@ -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.

20 changes: 20 additions & 0 deletions tests/framework_wagtail/dummy_app/apps.py
Original file line number Diff line number Diff line change
@@ -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"
40 changes: 40 additions & 0 deletions tests/framework_wagtail/dummy_app/models.py
Original file line number Diff line number Diff line change
@@ -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"]

Check failure on line 24 in tests/framework_wagtail/dummy_app/models.py

View workflow job for this annotation

GitHub Actions / MegaLinter

ruff (RUF005)

tests/framework_wagtail/dummy_app/models.py:24:22: RUF005 Consider `[*Page.content_panels, "body"]` instead of concatenation help: Replace with `[*Page.content_panels, "body"]`

class RoutablePage(RoutablePage):
body = RichTextField(blank=True)

content_panels = Page.content_panels + ["body"]

Check failure on line 30 in tests/framework_wagtail/dummy_app/models.py

View workflow job for this annotation

GitHub Actions / MegaLinter

ruff (RUF005)

tests/framework_wagtail/dummy_app/models.py:30:22: RUF005 Consider `[*Page.content_panels, "body"]` instead of concatenation help: Replace with `[*Page.content_panels, "body"]`
@re_path(r"^routable")
def index(self, request):
# Handle URLs of the form /<id>
return super().serve(request)

class Meta:
verbose_name = "Routable page"

def __str__(self):
return "Page from routable"
100 changes: 100 additions & 0 deletions tests/framework_wagtail/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
70 changes: 70 additions & 0 deletions tests/framework_wagtail/settings.py
Original file line number Diff line number Diff line change
@@ -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"),

Check failure on line 68 in tests/framework_wagtail/settings.py

View workflow job for this annotation

GitHub Actions / MegaLinter

ruff (PTH118)

tests/framework_wagtail/settings.py:68:17: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
}
}
Loading
Loading