Repository: wagtail/wagtail
File: wagtail/utils/widgets.py (Python)

Code before:
from warnings import warn

from django.forms.widgets import Widget
from django.utils.safestring import mark_safe

from wagtail.utils.deprecation import RemovedInWagtail70Warning


class WidgetWithScript(Widget):
    warn(
        "The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead.",
        category=RemovedInWagtail70Warning,
        stacklevel=2,
    )

    def render_html(self, name, value, attrs):
        """Render the HTML (non-JS) portion of the field markup"""
        return super().render(name, value, attrs)

    def get_value_data(self, value):
        # Perform any necessary preprocessing on the value passed to render() before it is passed
        # on to render_html / render_js_init. This is a good place to perform database lookups
        # that are needed by both render_html and render_js_init. Return value is arbitrary
        # (we only care that render_html / render_js_init can accept it), but will typically be
        # a dict of data needed for rendering: id, title etc.
        return value

    def render(self, name, value, attrs=None, renderer=None):
        # no point trying to come up with sensible semantics for when 'id' is missing from attrs,
        # so let's make sure it fails early in the process
        try:
            id_ = attrs["id"]
        except (KeyError, TypeError):
            raise TypeError(
                "WidgetWithScript cannot be rendered without an 'id' attribute"
            )

        value_data = self.get_value_data(value)

Review comment (suggestion):
Thanks! If this does work for you, then I think this is a bit tricky. I tried adding the following to the bakerydemo project:

```py
from django.forms import widgets
from wagtail.utils.widgets import WidgetWithScript


class CustomTextarea(WidgetWithScript, widgets.Textarea):
    pass
```

And I get the following without the `stacklevel` (default, `1`):

```
wagtail/wagtail/utils/widgets.py:10: RemovedInWagtail70Warning: The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead.
  warn(
```

With `stacklevel=2` (this PR):

```
wagtail/wagtail/utils/widgets.py:9: RemovedInWagtail70Warning: The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead.
  class WidgetWithScript(Widget):
```

With `stacklevel=3`:

```
bakerydemo/bakerydemo/base/wagtail_hooks.py:97: RemovedInWagtail70Warning: The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead.
  from wagtail.utils.widgets import WidgetWithScript
```

Of all those options, wouldn't `stacklevel=3` be the right one? How does it look on your end? 🤔 

Code after:
from warnings import warn

from django.forms.widgets import Widget
from django.utils.safestring import mark_safe

from wagtail.utils.deprecation import RemovedInWagtail70Warning


class WidgetWithScript(Widget):
    warn(
        "The usage of `WidgetWithScript` hook is deprecated. Use external scripts instead.",
        category=RemovedInWagtail70Warning,
        stacklevel=3,
    )

    def render_html(self, name, value, attrs):
        """Render the HTML (non-JS) portion of the field markup"""
        return super().render(name, value, attrs)

    def get_value_data(self, value):
        # Perform any necessary preprocessing on the value passed to render() before it is passed
        # on to render_html / render_js_init. This is a good place to perform database lookups
        # that are needed by both render_html and render_js_init. Return value is arbitrary
        # (we only care that render_html / render_js_init can accept it), but will typically be
        # a dict of data needed for rendering: id, title etc.
        return value

    def render(self, name, value, attrs=None, renderer=None):
        # no point trying to come up with sensible semantics for when 'id' is missing from attrs,
        # so let's make sure it fails early in the process
        try:
            id_ = attrs["id"]
        except (KeyError, TypeError):
            raise TypeError(
                "WidgetWithScript cannot be rendered without an 'id' attribute"
            )

        value_data = self.get_value_data(value)