Update panel templates for new designs (EditHandler rewrite)

Co-authored-by: Thibaud Colas <thibaudcolas@gmail.com>
pull/8948/head
Matt Westcott 2022-04-02 01:42:40 +01:00 zatwierdzone przez Thibaud Colas
rodzic 3b84559b0f
commit 5521e3b59f
20 zmienionych plików z 355 dodań i 339 usunięć

Wyświetl plik

@ -104,8 +104,6 @@ See {ref}`collapsible` for more details on `collapsible` usage.
Use of FieldRowPanel particularly helps reduce the "snow-blindness" effect of seeing so many fields on the page, for complex models. It also improves the perceived association between fields of a similar nature. For example if you created a model representing an "Event" which had a starting date and ending date, it may be intuitive to find the start and end date on the same "row".
By default, the panel is divided into equal-width columns, but this can be overridden by adding ``col*`` class names to each of the child Panels of the FieldRowPanel. The Wagtail editing interface is laid out using a grid system, in which the maximum width of the editor is 12 columns. Classes ``col1``-``col12`` can be applied to each child of a FieldRowPanel. The class ``col3`` will ensure that field appears 3 columns wide or a quarter the width. ``col4`` would cause the field to be 4 columns wide, or a third the width.
.. attribute:: FieldRowPanel.children
A ``list`` or ``tuple`` of child panels to display on the row

Wyświetl plik

@ -16,6 +16,7 @@
.. automethod:: get_form_options
.. automethod:: get_form_class
.. automethod:: get_bound_panel
.. autoproperty:: clean_name
```
## `BoundPanel`
@ -24,8 +25,13 @@
.. autoclass:: wagtail.admin.panels.Panel.BoundPanel
In addition to the standard template component functionality (see :ref:`creating_template_components`), this provides the following methods:
In addition to the standard template component functionality (see :ref:`creating_template_components`), this provides the following attributes and methods:
.. autoattribute:: panel
.. autoattribute:: instance
.. autoattribute:: request
.. autoattribute:: form
.. autoattribute:: prefix
.. automethod:: id_for_label
.. automethod:: is_shown
```

Wyświetl plik

@ -220,7 +220,7 @@ class CustomPanel(Panel):
# are available here
```
The template context for panels derived from `BaseChooserPanel` has changed. `BaseChooserPanel` is deprecated and now functionally identical to `FieldPanel`; as a result, the context variable `is_chosen`, and the variable name given by the panel's `object_type_name` property, are no longer available on the template. The only available variables are now `field` and `show_add_comment_button`. If your template depends on these additional variables, you will need to pass them explicitly by overriding the `render_as_field` method.
The template context for panels derived from `BaseChooserPanel` has changed. `BaseChooserPanel` is deprecated and now functionally identical to `FieldPanel`; as a result, the context variable `is_chosen`, and the variable name given by the panel's `object_type_name` property, are no longer available on the template. The only available variables are now `field` and `show_add_comment_button`. If your template depends on these additional variables, you will need to pass them explicitly by overriding the `BoundPanel.get_context_data` method.
### API changes to ModelAdmin

Wyświetl plik

@ -1,5 +1,4 @@
import functools
import re
from warnings import warn
from django import forms
@ -12,7 +11,6 @@ from django.dispatch import receiver
from django.forms import Media
from django.forms.formsets import DELETION_FIELD_NAME, ORDERING_FIELD_NAME
from django.forms.models import fields_for_model
from django.template.loader import render_to_string
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy
@ -24,7 +22,7 @@ from wagtail.admin.templatetags.wagtailadmin_tags import avatar_url, user_displa
from wagtail.admin.ui.components import Component
from wagtail.admin.widgets import AdminPageChooser
from wagtail.blocks import BlockField
from wagtail.coreutils import camelcase_to_underscore
from wagtail.coreutils import safe_snake_case
from wagtail.models import COMMENTS_RELATION_NAME, Page
from wagtail.utils.decorators import cached_classmethod
from wagtail.utils.deprecation import RemovedInWagtail50Warning
@ -225,7 +223,7 @@ class Panel:
)
return self.get_bound_panel(instance=instance, request=request, form=form)
def get_bound_panel(self, instance=None, request=None, form=None):
def get_bound_panel(self, instance=None, request=None, form=None, prefix="panel"):
"""
Return a ``BoundPanel`` instance that can be rendered onto the template as a component. By default, this creates an instance
of the panel class's inner ``BoundPanel`` class, which must inherit from ``Panel.BoundPanel``.
@ -243,7 +241,7 @@ class Panel:
)
return self.BoundPanel(
panel=self, instance=instance, request=request, form=form
panel=self, instance=instance, request=request, form=form, prefix=prefix
)
def on_model_bound(self):
@ -269,12 +267,6 @@ class Panel:
return [self.classname]
return []
def field_type(self):
"""
The kind of field it is e.g boolean_field. Useful for better semantic markup of field display based on type
"""
return ""
def id_for_label(self):
"""
The ID to be used as the 'for' attribute of any <label> elements that refer
@ -283,17 +275,36 @@ class Panel:
"""
return ""
@property
def clean_name(self):
"""
A name for this panel, consisting only of ASCII alphanumerics and underscores, suitable for use in identifiers.
Usually generated from the panel heading. Note that this is not guaranteed to be unique or non-empty; anything
making use of this and requiring uniqueness should validate and modify the return value as needed.
"""
return safe_snake_case(self.heading)
class BoundPanel(Component):
"""
A template component for a panel that has been associated with a model instance, form, and request.
"""
def __init__(self, panel, instance, request, form):
def __init__(self, panel, instance, request, form, prefix):
#: The panel definition corresponding to this bound panel
self.panel = panel
#: The model instance associated with this panel
self.instance = instance
#: The request object associated with this panel
self.request = request
#: The form object associated with this panel
self.form = form
#: A unique prefix for this panel, for use in HTML IDs
self.prefix = prefix
self.heading = self.panel.heading
self.help_text = self.panel.help_text
@ -304,9 +315,6 @@ class Panel:
def classes(self):
return self.panel.classes()
def field_type(self):
return self.panel.field_type()
def id_for_label(self):
"""
Returns an HTML ID to be used as the target for any label referencing this panel.
@ -319,10 +327,23 @@ class Panel:
"""
return True
def is_required(self):
return False
def render_as_object(self):
warn(
"Panel.render_as_object is deprecated. Use render_html instead",
category=RemovedInWagtail50Warning,
stacklevel=2,
)
return self.render_html()
def render_as_field(self):
warn(
"Panel.render_as_field is deprecated. Use render_html instead",
category=RemovedInWagtail50Warning,
stacklevel=2,
)
return self.render_html()
def get_context_data(self, parent_context=None):
@ -355,7 +376,7 @@ class Panel:
Render this as an 'object', ensuring that all fields necessary for a valid form
submission are included
"""
return mark_safe(self.render_as_object() + self.render_missing_fields())
return mark_safe(self.render_html() + self.render_missing_fields())
def __repr__(self):
return "<%s with model=%s instance=%s request=%s form=%s>" % (
@ -443,23 +464,56 @@ class PanelGroup(Panel):
def on_model_bound(self):
self.children = [child.bind_to_model(self.model) for child in self.children]
class BoundPanel(Panel.BoundPanel):
def __init__(self, panel, instance, request, form):
super().__init__(panel=panel, instance=instance, request=request, form=form)
@cached_property
def child_identifiers(self):
"""
A list of identifiers corresponding to child panels in ``self.children``, formed from the clean_name property
but validated to be unique and non-empty.
"""
used_names = set()
result = []
for panel in self.children:
base_name = panel.clean_name or "panel"
candidate_name = base_name
suffix = 0
while candidate_name in used_names:
suffix += 1
candidate_name = "%s%d" % (base_name, suffix)
result.append(candidate_name)
used_names.add(candidate_name)
return result
class BoundPanel(Panel.BoundPanel):
@cached_property
def children(self):
return [
child.get_bound_panel(
instance=self.instance, request=self.request, form=self.form
instance=self.instance,
request=self.request,
form=self.form,
prefix=("%s-child-%s" % (self.prefix, identifier)),
)
for child, identifier in zip(
self.panel.children, self.panel.child_identifiers
)
for child in self.panel.children
]
@cached_property
def visible_children(self):
return [child for child in self.children if child.is_shown()]
@cached_property
def visible_children_with_identifiers(self):
return [
(child, identifier)
for child, identifier in zip(
self.children, self.panel.child_identifiers
)
if child.is_shown()
]
def is_shown(self):
return any(child.is_shown() for child in self.children)
@ -501,24 +555,10 @@ class ObjectList(PanelGroup):
class FieldRowPanel(PanelGroup):
class BoundPanel(PanelGroup.BoundPanel):
template_name = "wagtailadmin/panels/field_row_panel.html"
def visible_children_with_classnames(self):
visible_children = self.visible_children
col_count = " col%s" % (12 // len(visible_children))
for child in visible_children:
classname = " ".join(child.classes())
if not re.search(r"\bcol\d+\b", classname):
classname += col_count
yield child, classname
template_name = "wagtailadmin/panels/multi_field_panel.html"
class MultiFieldPanel(PanelGroup):
def classes(self):
classes = super().classes()
classes.append("multi-field")
return classes
class BoundPanel(PanelGroup.BoundPanel):
template_name = "wagtailadmin/panels/multi_field_panel.html"
@ -543,9 +583,13 @@ class HelpPanel(Panel):
)
return kwargs
@property
def clean_name(self):
return super().clean_name or "help"
class BoundPanel(Panel.BoundPanel):
def __init__(self, panel, instance, request, form):
super().__init__(panel, instance, request, form)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.template_name = self.panel.template
self.content = self.panel.content
@ -617,6 +661,10 @@ class FieldPanel(Panel):
return model._meta.get_field(self.field_name)
@property
def clean_name(self):
return self.field_name
def __repr__(self):
return "<%s '%s' with model=%s>" % (
self.__class__.__name__,
@ -625,11 +673,10 @@ class FieldPanel(Panel):
)
class BoundPanel(Panel.BoundPanel):
object_template_name = "wagtailadmin/panels/single_field_panel.html"
field_template_name = "wagtailadmin/panels/field_panel_field.html"
template_name = "wagtailadmin/panels/field_panel.html"
def __init__(self, panel, instance, request, form):
super().__init__(panel=panel, instance=instance, request=request, form=form)
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.form is None:
self.bound_field = None
@ -666,26 +713,11 @@ class FieldPanel(Panel):
return True
def is_required(self):
return self.bound_field.field.required
def classes(self):
classes = self.panel.classes().copy()
if self.bound_field.field.required:
classes.append("required")
# If field has any errors, add the classname 'error' to enable error styling
# (e.g. red background), unless the widget has its own mechanism for rendering errors
# via the render_with_errors mechanism (as StreamField does).
if self.bound_field.errors and not hasattr(
self.bound_field.field.widget, "render_with_errors"
):
classes.append("error")
classes.append(self.field_type())
return classes
def field_type(self):
return camelcase_to_underscore(self.bound_field.field.__class__.__name__)
return self.panel.classes()
def id_for_label(self):
return self.bound_field.id_for_label
@ -698,32 +730,66 @@ class FieldPanel(Panel):
else:
return not self.panel.disable_comments
def render_as_object(self):
return render_to_string(
self.object_template_name,
{
"self": self,
self.panel.TEMPLATE_VAR: self,
"field": self.bound_field,
"show_add_comment_button": self.comments_enabled
and getattr(
self.bound_field.field.widget, "show_add_comment_button", True
),
},
)
def get_context_data(self, parent_context=None):
context = super().get_context_data(parent_context)
def render_as_field(self):
return render_to_string(
self.field_template_name,
widget_described_by_ids = []
help_text = self.bound_field.help_text
help_text_id = "%s-helptext" % self.prefix
errors = None
error_message_id = "%s-errors" % self.prefix
if help_text:
widget_described_by_ids.append(help_text_id)
if self.bound_field.errors:
widget = self.bound_field.field.widget
if hasattr(widget, "render_with_errors"):
widget_attrs = {
"id": self.bound_field.auto_id,
}
if widget_described_by_ids:
widget_attrs["aria-describedby"] = " ".join(
widget_described_by_ids
)
rendered_field = widget.render_with_errors(
self.bound_field.html_name,
self.bound_field.value(),
attrs=widget_attrs,
errors=self.bound_field.errors,
)
else:
errors = self.bound_field.errors
widget_described_by_ids.append(error_message_id)
rendered_field = self.bound_field.as_widget(
attrs={
"aria-invalid": "true",
"aria-describedby": " ".join(widget_described_by_ids),
}
)
else:
widget_attrs = {}
if widget_described_by_ids:
widget_attrs["aria-describedby"] = " ".join(widget_described_by_ids)
rendered_field = self.bound_field.as_widget(attrs=widget_attrs)
context.update(
{
"field": self.bound_field,
"field_type": self.field_type(),
"rendered_field": rendered_field,
"help_text": help_text,
"help_text_id": help_text_id,
"errors": errors,
"error_message_id": error_message_id,
"show_add_comment_button": self.comments_enabled
and getattr(
self.bound_field.field.widget, "show_add_comment_button", True
),
},
}
)
return context
def get_comparison(self):
comparator_class = self.panel.get_comparison_class()
@ -862,8 +928,8 @@ class InlinePanel(Panel):
class BoundPanel(Panel.BoundPanel):
template_name = "wagtailadmin/panels/inline_panel.html"
def __init__(self, panel, instance, request, form):
super().__init__(panel, instance, request, form)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.label = self.panel.label
@ -874,7 +940,7 @@ class InlinePanel(Panel):
self.child_edit_handler = self.panel.child_edit_handler
self.children = []
for subform in self.formset.forms:
for index, subform in enumerate(self.formset.forms):
# override the DELETE field to have a hidden input
subform.fields[DELETION_FIELD_NAME].widget = forms.HiddenInput()
@ -884,7 +950,10 @@ class InlinePanel(Panel):
self.children.append(
self.child_edit_handler.get_bound_panel(
instance=subform.instance, request=self.request, form=subform
instance=subform.instance,
request=self.request,
form=subform,
prefix=("%s-%d" % (self.prefix, index)),
)
)
@ -901,16 +970,22 @@ class InlinePanel(Panel):
empty_form.fields[ORDERING_FIELD_NAME].widget = forms.HiddenInput()
self.empty_child = self.child_edit_handler.get_bound_panel(
instance=empty_form.instance, request=self.request, form=empty_form
instance=empty_form.instance,
request=self.request,
form=empty_form,
prefix=("%s-__prefix__" % self.prefix),
)
def get_comparison(self):
field_comparisons = []
for panel in self.panel.child_edit_handler.children:
for index, panel in enumerate(self.panel.child_edit_handler.children):
field_comparisons.extend(
panel.get_bound_panel(
instance=None, request=self.request, form=None
instance=None,
request=self.request,
form=None,
prefix=("%s-%d" % (self.prefix, index)),
).get_comparison()
)
@ -968,6 +1043,10 @@ class CommentPanel(Panel):
},
}
@property
def clean_name(self):
return super().clean_name or "commments"
class BoundPanel(Panel.BoundPanel):
template_name = "wagtailadmin/panels/comments/comment_panel.html"

Wyświetl plik

@ -0,0 +1,20 @@
{% load wagtailadmin_tags i18n %}
<div id="{{ self.prefix }}-field" class="w-field {% if field.errors %}w-field--error{% endif %}" data-contentpath="{{ field.name }}">
{% if errors %}
<p id="{{ error_message_id }}" class="w-error"><svg width="16" height="16"><use href="#icon-cross"></use></svg>{% for error in errors %}{{ error }} {% endfor %}</p>
{% endif %}
{{ rendered_field }}
{% if help_text %}
<p id="{{ help_text_id }}" class="w-help-text">{{ help_text }}</p>
{% endif %}
{% if show_add_comment_button %}
<div class="field-comment-control">
<button type="button" data-component="add-comment-button" data-comment-add class="u-hidden" aria-label="{% trans 'Add comment' %}">
{% icon name="comment-add" class_name="initial icon-default" %}
{% icon name="comment-add-reversed" class_name="initial icon-reversed" %}
</button>
</div>
{% endif %}
</div>

Wyświetl plik

@ -1 +0,0 @@
{% include "wagtailadmin/shared/field.html" %}

Wyświetl plik

@ -1,7 +0,0 @@
<ul class="field-row {{ self.classes|join:" " }}">
{% for child, classname in self.visible_children_with_classnames %}
<li class="field-col {{ classname }}">
{{ child.render_as_field }}
</li>
{% endfor %}
</ul>

Wyświetl plik

@ -1,8 +1,6 @@
<fieldset>
<legend>{{ self.heading }}</legend>
<ul class="fields">
{% for child in self.visible_children %}
<li class="{{ child.classes|join:" " }}">{{ child.render_as_field }}</li>
{% endfor %}
</ul>
</fieldset>
{% load wagtailadmin_tags %}
{% for child in self.visible_children %}
<label {% if child.id_for_label %}for="{{ child.id_for_label }}"{% endif %} class="w-field__label">{{ child.heading }}{% if child.is_required %}<span class="w-error" aria-hidden="true">*</span>{% endif %}</label>
{% component child %}
{% endfor %}

Wyświetl plik

@ -1,28 +1,33 @@
{% load wagtailadmin_tags %}
{% load wagtailadmin_tags %}
<ul class="objects">
{% for child in self.visible_children %}
<li class="object {{ child.classes|join:" " }}">
{% if child.heading %}
<div class="title-wrapper">
<label {% if child.id_for_label %}for="{{ child.id_for_label }}"{% endif %}>
{{ child.heading }}
</label>
</div>
{% endif %}
<div class="object-layout">
{% if child.help_text %}
<div class="object-layout_small-part">
<div class="object-help help">
{% icon name="help" class_name="default" %}
{{ child.help_text }}
</div>
</div>
{% for child, identifier in self.visible_children_with_identifiers %}
<section
id="{{ self.prefix }}-childsection-{{ identifier }}"
aria-labelledby="{{ self.prefix }}-childheading-{{ identifier }}"
class="w-panel"
>
<div class="w-panel__header">
<a href="#{{ self.prefix }}-childsection-{{ identifier }}" aria-label="Link to {{ child.heading }}">#</a>
<button
type="button"
class="w-panel__toggle"
aria-label="Toggle {{ child.heading }}"
aria-controls="{{ self.prefix }}-childcontent-{{ identifier }}"
aria-expanded="true"
>
<svg width="16" height="16"><use href="#icon-pilcrow"></use></svg>
</button>
<h2 id="{{ self.prefix }}-childheading-{{ identifier }}" class="w-panel__heading">
{% if child.id_for_label %}
<label for="{{ child.id_for_label }}">{{ child.heading }}{% if child.is_required %}<span class="w-error" aria-hidden="true">*</span>{% endif %}</label>
{% else %}
{{ child.heading }}{% if child.is_required %}<span class="w-error" aria-hidden="true">*</span>{% endif %}
{% endif %}
<div class="object-layout_big-part">
{{ child.render_as_object }}
</div>
</div>
</li>
{% endfor %}
</ul>
</h2>
</div>
<div id="{{ self.prefix }}-childcontent-{{ identifier }}" class="w-panel__content">
{% component child %}
</div>
</section>
{% endfor %}

Wyświetl plik

@ -1,21 +0,0 @@
{% load i18n wagtailadmin_tags %}
<div data-contentpath="{{ self.field_name }}">
<fieldset>
<legend>{{ self.heading }}</legend>
<ul class="fields">
<li>
{% include "wagtailadmin/shared/field.html" with show_label=False show_help_text=False show_add_comment_button=False include_contentpath=False %}
</li>
</ul>
</fieldset>
{% if show_add_comment_button %}
<div class="field-comment-control field-comment-control--object">
<button type="button" data-component="add-comment-button" data-comment-add class="u-hidden" aria-label="{% trans 'Add comment' %}">
{% icon name="comment-add" class_name="initial icon-default" %}
{% icon name="comment-add-reversed" class_name="initial icon-reversed" %}
</button>
</div>
{% endif %}
</div>

Wyświetl plik

@ -3,8 +3,8 @@
<div class="w-tabs" data-tabs>
<div class="w-tabs__wrapper">
<div role="tablist" class="w-tabs__list">
{% for child in self.visible_children %}
{% include 'wagtailadmin/shared/tabs/tab_nav_link.html' with tab_id=child.heading title=child.heading classes=child.classes|join:" " %}
{% for child, identifier in self.visible_children_with_identifiers %}
{% include 'wagtailadmin/shared/tabs/tab_nav_link.html' with tab_id=identifier title=child.heading classes=child.classes|join:" " %}
{% endfor %}
</div>
@ -21,15 +21,15 @@
</div>
<div class="tab-content">
{% for child in self.visible_children %}
{% for child, identifier in self.visible_children_with_identifiers %}
<section
id="tab-{{ child.heading|cautious_slugify }}"
id="tab-{{ identifier }}"
class="w-tabs__panel {{ child.classes|join:" " }}"
role="tabpanel"
aria-labelledby="tab-label-{{ child.heading|cautious_slugify }}"
aria-labelledby="tab-label-{{ identifier }}"
hidden
>
{{ child.render_as_object }}
{{ child.render_html }}
</section>
{% endfor %}
</div>

Wyświetl plik

@ -1,16 +1,16 @@
{% load wagtailadmin_tags i18n %}
{% load i18n %}
{% comment %}
Variables accepted by this template:
- `tab_id` - {string} A unique tab id
- `tab_id` - {string} A unique tab id consisting only of ASCII alphanumerics, dashes and underscores
- `title` - {string} Text that the tab button will display
- `active` - {boolean?} Force this to be active
- `classes` - {string?} Extra css classes to pass to this component
- `errors_count` - {number?} Show above the tab for errors count
{% endcomment %}
<a id="tab-label-{{ tab_id|cautious_slugify }}" href="#tab-{{ tab_id|cautious_slugify }}" class="w-tabs__tab {{ classes }}" role="tab" aria-selected="false" tabindex="-1">
<a id="tab-label-{{ tab_id }}" href="#tab-{{ tab_id }}" class="w-tabs__tab {{ classes }}" role="tab" aria-selected="false" tabindex="-1">
<div data-tabs-errors class="w-tabs__errors {% if errors_count %}!w-flex{% endif %}">
<span class="w-sr-only">{% trans 'Errors Count: ' %}</span>
<span data-tabs-errors-count>{{ errors_count }}</span>

Wyświetl plik

@ -117,7 +117,10 @@ class TestPageEdit(TestCase, WagtailTestUtils):
self.assertContains(response, 'id="status-sidebar-live"')
# Test InlinePanel labels/headings
self.assertContains(response, "<legend>Speaker lineup</legend>")
self.assertContains(
response,
'<label for="id_speakers-__prefix__-last_name" class="w-field__label">Surname</label>',
)
self.assertContains(response, "Add speakers")
# test register_page_action_menu_item hook
@ -985,8 +988,8 @@ class TestPageEdit(TestCase, WagtailTestUtils):
reverse("wagtailadmin_pages:edit", args=(self.child_page.id,))
)
input_field_for_draft_slug = '<input type="text" name="slug" value="revised-slug-in-draft-only" id="id_slug" maxlength="255" required />'
input_field_for_live_slug = '<input type="text" name="slug" value="hello-world" id="id_slug" maxlength="255" required />'
input_field_for_draft_slug = '<input type="text" name="slug" value="revised-slug-in-draft-only" aria-describedby="panel-child-promote-child-for_search_engines-child-slug-helptext" id="id_slug" maxlength="255" required />'
input_field_for_live_slug = '<input type="text" name="slug" value="hello-world" aria-describedby="panel-child-promote-child-for_search_engines-child-slug-helptext" id="id_slug" maxlength="255" required />'
# Status Link should be the live page (not revision)
self.assertNotContains(
@ -1011,8 +1014,8 @@ class TestPageEdit(TestCase, WagtailTestUtils):
reverse("wagtailadmin_pages:edit", args=(self.single_event_page.id,))
)
input_field_for_draft_slug = '<input type="text" name="slug" value="revised-slug-in-draft-only" id="id_slug" maxlength="255" required />'
input_field_for_live_slug = '<input type="text" name="slug" value="mars-landing" id="id_slug" maxlength="255" required />'
input_field_for_draft_slug = '<input type="text" name="slug" value="revised-slug-in-draft-only" aria-describedby="panel-child-promote-child-common_page_configuration-child-slug-helptext" id="id_slug" maxlength="255" required />'
input_field_for_live_slug = '<input type="text" name="slug" value="mars-landing" aria-describedby="panel-child-promote-child-common_page_configuration-child-slug-helptext" id="id_slug" maxlength="255" required />'
# Status Link should be the live page (not revision)
self.assertNotContains(
@ -2053,9 +2056,8 @@ class TestValidationErrorMessages(TestCase, WagtailTestUtils):
# the error should only appear once: against the field, not in the header message
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=1,
html=True,
)
self.assertContains(response, "This field is required", count=1)
@ -2146,9 +2148,8 @@ class TestValidationErrorMessages(TestCase, WagtailTestUtils):
# Error on title shown against the title field
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=1,
html=True,
)
# Error on title shown in the header message
self.assertContains(

Wyświetl plik

@ -387,7 +387,7 @@ class TestCompareRevisionsWithNonModelField(TestCase, WagtailTestUtils):
response = self.client.get(edit_url)
self.assertContains(
response,
'<input type="text" name="code" required id="id_code" maxlength="5" />',
'<input type="text" name="code" aria-describedby="panel-child-content-child-code-helptext" required id="id_code" maxlength="5" />',
html=True,
)

Wyświetl plik

@ -425,7 +425,7 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
# result should contain tab buttons
self.assertIn(
'<a id="tab-label-event-details" href="#tab-event-details" class="w-tabs__tab shiny" role="tab" aria-selected="false" tabindex="-1">',
'<a id="tab-label-event_details" href="#tab-event_details" class="w-tabs__tab shiny" role="tab" aria-selected="false" tabindex="-1">',
result,
)
self.assertIn(
@ -434,7 +434,7 @@ class TestTabbedInterface(TestCase, WagtailTestUtils):
)
# result should contain tab panels
self.assertIn('aria-labelledby="tab-label-event-details"', result)
self.assertIn('aria-labelledby="tab-label-event_details"', result)
self.assertIn('aria-labelledby="tab-label-speakers"', result)
# result should contain rendered content from descendants
@ -560,14 +560,17 @@ class TestObjectList(TestCase):
result = object_list.render_html()
# result should contain ObjectList furniture
self.assertIn('<ul class="objects">', result)
self.assertIn('<div class="w-panel__header">', result)
# result should contain labels for children
self.assertInHTML('<label for="id_date_from">Start date</label>', result)
self.assertInHTML(
'<label for="id_date_from">Start date<span class="w-error" aria-hidden="true">*</span></label>',
result,
)
# result should include help text for children
self.assertInHTML(
'<div class="object-help help"> <svg class="icon icon-help default" aria-hidden="true"><use href="#icon-help"></use></svg> Not required if event is on a single day</div>',
'<p id="panel-child-date_to-helptext" class="w-help-text">Not required if event is on a single day</p>',
result,
)
@ -632,7 +635,7 @@ class TestFieldPanel(TestCase):
end_date_panel_with_overridden_heading.bound_field.label, "New heading"
)
def test_render_as_object(self):
def test_render_html(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
@ -649,53 +652,17 @@ class TestFieldPanel(TestCase):
form=form,
request=self.request,
)
result = field_panel.render_as_object()
result = field_panel.render_html()
# check that label appears as a legend in the 'object' wrapper,
# but not as a field label (that would be provided by ObjectList instead)
self.assertIn("<legend>End date</legend>", result)
self.assertNotIn('<label for="id_date_to">End date:</label>', result)
# check that help text is not included (it's provided by ObjectList instead)
self.assertNotIn("Not required if event is on a single day", result)
# check that the populated form field is included
self.assertIn('value="2014-07-22"', result)
# there should be no errors on this field
self.assertNotIn('<p class="error-message">', result)
def test_render_as_field(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
"date_from": "2014-07-20",
"date_to": "2014-07-22",
},
instance=self.event,
)
form.is_valid()
field_panel = self.end_date_panel.get_bound_panel(
instance=self.event,
form=form,
request=self.request,
)
result = field_panel.render_as_field()
# check that label is output in the 'field' style
self.assertIn('<label for="id_date_to">End date:</label>', result)
self.assertNotIn("<legend>End date</legend>", result)
# check that help text is included
self.assertIn("Not required if event is on a single day", result)
# check that the populated form field is included
self.assertIn('value="2014-07-22"', result)
# there should be no errors on this field
self.assertNotIn('<p class="error-message">', result)
self.assertNotIn(
'<svg width="16" height="16"><use href="#icon-cross"></use></svg>', result
)
def test_required_fields(self):
result = self.end_date_panel.get_form_options()["fields"]
@ -718,10 +685,12 @@ class TestFieldPanel(TestCase):
form=form,
request=self.request,
)
result = field_panel.render_as_field()
result = field_panel.render_html()
self.assertIn('<p class="error-message">', result)
self.assertIn("<span>Enter a valid date.</span>", result)
self.assertIn(
'<svg width="16" height="16"><use href="#icon-cross"></use></svg>Enter a valid date.',
result,
)
def test_repr(self):
form = self.EventPageForm()
@ -766,7 +735,7 @@ class TestFieldRowPanel(TestCase):
]
).bind_to_model(EventPage)
def test_render_as_object(self):
def test_render_html(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
@ -783,39 +752,18 @@ class TestFieldRowPanel(TestCase):
form=form,
request=self.request,
)
result = field_panel.render_as_object()
# check that the populated form field is included
self.assertIn('value="2014-07-22"', result)
# there should be no errors on this field
self.assertNotIn('<p class="error-message">', result)
def test_render_as_field(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
"date_from": "2014-07-20",
"date_to": "2014-07-22",
},
instance=self.event,
)
form.is_valid()
field_panel = self.dates_panel.get_bound_panel(
instance=self.event,
form=form,
request=self.request,
)
result = field_panel.render_as_field()
result = field_panel.render_html()
# check that label is output in the 'field' style
self.assertIn('<label for="id_date_to">End date:</label>', result)
self.assertNotIn("<legend>End date</legend>", result)
self.assertIn(
'<label for="id_date_to" class="w-field__label">End date</label>', result
)
# check that label is overridden with the 'heading' argument
self.assertIn('<label for="id_date_from">Start:</label>', result)
self.assertIn(
'<label for="id_date_from" class="w-field__label">Start<span class="w-error" aria-hidden="true">*</span></label>',
result,
)
# check that help text is included
self.assertIn("Not required if event is on a single day", result)
@ -824,7 +772,9 @@ class TestFieldRowPanel(TestCase):
self.assertIn('value="2014-07-22"', result)
# there should be no errors on this field
self.assertNotIn('<p class="error-message">', result)
self.assertNotIn(
'<svg width="16" height="16"><use href="#icon-cross"></use></svg>', result
)
def test_error_message_is_rendered(self):
form = self.EventPageForm(
@ -843,55 +793,13 @@ class TestFieldRowPanel(TestCase):
form=form,
request=self.request,
)
result = field_panel.render_as_field()
result = field_panel.render_html()
self.assertIn('<p class="error-message">', result)
self.assertIn("<span>Enter a valid date.</span>", result)
def test_add_col_when_wrong_in_panel_def(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
"date_from": "2014-07-20",
"date_to": "2014-07-33",
},
instance=self.event,
self.assertIn(
'<svg width="16" height="16"><use href="#icon-cross"></use></svg>Enter a valid date.',
result,
)
form.is_valid()
field_panel = self.dates_panel.get_bound_panel(
instance=self.event,
form=form,
request=self.request,
)
result = field_panel.render_as_field()
self.assertIn('<li class="field-col coltwo error date_field col6">', result)
def test_added_col_doesnt_change_siblings(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
"date_from": "2014-07-20",
"date_to": "2014-07-33",
},
instance=self.event,
)
form.is_valid()
field_panel = self.dates_panel.get_bound_panel(
instance=self.event,
form=form,
request=self.request,
)
result = field_panel.render_as_field()
self.assertIn('<li class="field-col col4', result)
class TestFieldRowPanelWithChooser(TestCase):
def setUp(self):
@ -918,7 +826,7 @@ class TestFieldRowPanelWithChooser(TestCase):
]
).bind_to_model(EventPage)
def test_render_as_object(self):
def test_render_html(self):
form = self.EventPageForm(
{
"title": "Pontypridd sheepdog trials",
@ -935,13 +843,15 @@ class TestFieldRowPanelWithChooser(TestCase):
form=form,
request=self.request,
)
result = field_panel.render_as_object()
result = field_panel.render_html()
# check that the populated form field is included
self.assertIn('value="2014-07-20"', result)
# there should be no errors on this field
self.assertNotIn('<p class="error-message">', result)
self.assertNotIn(
'<svg width="16" height="16"><use href="#icon-cross"></use></svg>', result
)
class TestPageChooserPanel(TestCase):
@ -977,7 +887,7 @@ class TestPageChooserPanel(TestCase):
self.assertEqual(type(self.form.fields["page"].widget), AdminPageChooser)
def test_render_js_init(self):
result = self.page_chooser_panel.render_as_field()
result = self.page_chooser_panel.render_html()
expected_js = 'new PageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
id="id_page", model="wagtailcore.page", parent=self.events_index_page.id
)
@ -997,7 +907,7 @@ class TestPageChooserPanel(TestCase):
page_chooser_panel = my_page_chooser_panel.get_bound_panel(
instance=self.test_instance, form=form, request=self.request
)
result = page_chooser_panel.render_as_field()
result = page_chooser_panel.render_html()
# the canChooseRoot flag on PageChooser should now be true
expected_js = 'new PageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": true, "user_perms": null}});'.format(
@ -1005,9 +915,11 @@ class TestPageChooserPanel(TestCase):
)
self.assertIn(expected_js, result)
def test_render_as_field(self):
result = self.page_chooser_panel.render_as_field()
self.assertIn('<p class="help">help text</p>', result)
def test_render_html(self):
result = self.page_chooser_panel.render_html()
self.assertIn(
'<p id="panel-helptext" class="w-help-text">help text</p>', result
)
self.assertIn('<span class="title">Christmas</span>', result)
self.assertIn(
'<a href="/admin/pages/%d/edit/" class="edit-link button button-small button-secondary" target="_blank" rel="noreferrer">'
@ -1021,9 +933,11 @@ class TestPageChooserPanel(TestCase):
page_chooser_panel = self.my_page_chooser_panel.get_bound_panel(
instance=test_instance, form=form, request=self.request
)
result = page_chooser_panel.render_as_field()
result = page_chooser_panel.render_html()
self.assertIn('<p class="help">help text</p>', result)
self.assertIn(
'<p id="panel-helptext" class="w-help-text">help text</p>', result
)
self.assertIn('<span class="title"></span>', result)
self.assertIn("Choose a page", result)
@ -1035,7 +949,8 @@ class TestPageChooserPanel(TestCase):
instance=self.test_instance, form=form, request=self.request
)
self.assertIn(
"<span>This field is required.</span>", page_chooser_panel.render_as_field()
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
page_chooser_panel.render_html(),
)
def test_override_page_type(self):
@ -1051,8 +966,16 @@ class TestPageChooserPanel(TestCase):
instance=self.test_instance, form=form, request=self.request
)
<<<<<<< HEAD
result = page_chooser_panel.render_as_field()
expected_js = 'new PageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
||||||| parent of 2346b6cb88 (Drop render_as_object / render_as_field distinction)
result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
=======
result = page_chooser_panel.render_html()
expected_js = 'createPageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
>>>>>>> 2346b6cb88 (Drop render_as_object / render_as_field distinction)
id="id_page", model="tests.eventpage", parent=self.events_index_page.id
)
@ -1071,8 +994,16 @@ class TestPageChooserPanel(TestCase):
instance=self.test_instance, form=form, request=self.request
)
<<<<<<< HEAD
result = page_chooser_panel.render_as_field()
expected_js = 'new PageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
||||||| parent of 2346b6cb88 (Drop render_as_object / render_as_field distinction)
result = page_chooser_panel.render_as_field()
expected_js = 'createPageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
=======
result = page_chooser_panel.render_html()
expected_js = 'createPageChooser("{id}", {parent}, {{"model_names": ["{model}"], "can_choose_root": false, "user_perms": null}});'.format(
>>>>>>> 2346b6cb88 (Drop render_as_object / render_as_field distinction)
id="id_page", model="tests.eventpage", parent=self.events_index_page.id
)
@ -1128,13 +1059,23 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
instance=event_page, form=form, request=self.request
)
result = panel.render_as_field()
result = panel.render_html()
self.assertIn('<li class="object classname-for-speakers">', result)
self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result)
# FIXME: reinstate when we pass classnames to the template again
# self.assertIn('<li class="object classname-for-speakers">', result)
self.assertIn(
'<label for="id_speakers-0-first_name" class="w-field__label">Name</label>',
result,
)
self.assertIn('value="Father"', result)
self.assertIn('<label for="id_speakers-0-last_name">Surname:</label>', result)
self.assertIn('<label for="id_speakers-0-image">Image:</label>', result)
self.assertIn(
'<label for="id_speakers-0-last_name" class="w-field__label">Surname</label>',
result,
)
self.assertIn(
'<label for="id_speakers-0-image" class="w-field__label">Image</label>',
result,
)
self.assertIn("Choose an image", result)
# rendered panel must also contain hidden fields for id, DELETE and ORDER
@ -1194,10 +1135,13 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
instance=event_page, form=form, request=self.request
)
result = panel.render_as_field()
result = panel.render_html()
# rendered panel should contain first_name rendered as a text area, but no last_name field
self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result)
self.assertIn(
'<label for="id_speakers-0-first_name" class="w-field__label">Name</label>',
result,
)
self.assertIn("Father</textarea>", result)
self.assertNotIn(
'<label for="id_speakers-0-last_name">Surname:</label>', result
@ -1211,7 +1155,7 @@ class TestInlinePanel(TestCase, WagtailTestUtils):
allow_extra_attrs=True,
)
self.assertIn('<label for="id_speakers-0-image">Image:</label>', result)
# self.assertIn('<label for="id_speakers-0-image">Image:</label>', result)
self.assertIn("Choose an image", result)
# rendered panel must also contain hidden fields for id, DELETE and ORDER

Wyświetl plik

@ -2460,7 +2460,7 @@ class TestWorkflowStatus(TestCase, WagtailTestUtils):
response = self.client.get(self.edit_url)
needle = "This page is awaiting <b>'test_task_1'</b> in the <b>'test_workflow'</b> workflow. Only reviewers for this task can edit the page."
self.assertContains(response, needle)
self.assertContains(response, needle, count=1)
self.login(self.moderator)
response = self.client.get(self.edit_url)

Wyświetl plik

@ -413,9 +413,8 @@ class TestCreateView(TestCase, WagtailTestUtils):
self.assertFormError(response, "form", "title", "This field is required.")
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=1,
html=True,
)
def test_exclude_passed_to_extract_panel_definitions(self):
@ -687,9 +686,8 @@ class TestEditView(TestCase, WagtailTestUtils):
self.assertFormError(response, "form", "title", "This field is required.")
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=1,
html=True,
)
def test_exclude_passed_to_extract_panel_definitions(self):

Wyświetl plik

@ -94,9 +94,8 @@ class TestSiteSettingCreateView(BaseTestSiteSettingView):
self.assertContains(response, "The setting could not be saved due to errors.")
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=2,
html=True,
)
self.assertContains(response, "This field is required", count=2)
@ -148,9 +147,8 @@ class TestSiteSettingEditView(BaseTestSiteSettingView):
self.assertContains(response, "The setting could not be saved due to errors.")
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=2,
html=True,
)
self.assertContains(response, "This field is required", count=2)

Wyświetl plik

@ -525,9 +525,8 @@ class TestSnippetCreateView(TestCase, WagtailTestUtils):
self.assertContains(response, "The snippet could not be created due to errors.")
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=1,
html=True,
)
self.assertContains(response, "This field is required", count=1)
@ -934,9 +933,8 @@ class TestSnippetEditView(BaseTestSnippetEditView):
self.assertContains(response, "The snippet could not be saved due to errors.")
self.assertContains(
response,
"""<p class="error-message"><span>This field is required.</span></p>""",
"""<svg width="16" height="16"><use href="#icon-cross"></use></svg>This field is required.""",
count=1,
html=True,
)
self.assertContains(response, "This field is required", count=1)
@ -1757,8 +1755,8 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
if getattr(panel, "field_name", None) == "advert"
][0]
def test_render_as_field(self):
field_html = self.snippet_chooser_panel.render_as_field()
def test_render_html(self):
field_html = self.snippet_chooser_panel.render_html()
self.assertIn(self.advert_text, field_html)
self.assertIn("Choose advert", field_html)
self.assertIn("Choose another advert", field_html)
@ -1776,14 +1774,14 @@ class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
if getattr(panel, "field_name", None) == "advert"
][0]
field_html = snippet_chooser_panel.render_as_field()
field_html = snippet_chooser_panel.render_html()
self.assertIn("Choose advert", field_html)
self.assertIn("Choose another advert", field_html)
def test_render_js(self):
self.assertIn(
'new SnippetChooser("id_advert");',
self.snippet_chooser_panel.render_as_field(),
self.snippet_chooser_panel.render_html(),
)
def test_target_model_autodetected(self):
@ -3120,8 +3118,8 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
if getattr(panel, "field_name", None) == "advertwithcustomprimarykey"
][0]
def test_render_as_field(self):
field_html = self.snippet_chooser_panel.render_as_field()
def test_render_html(self):
field_html = self.snippet_chooser_panel.render_html()
self.assertIn(self.advert_text, field_html)
self.assertIn("Choose advert with custom primary key", field_html)
self.assertIn("Choose another advert with custom primary key", field_html)
@ -3139,14 +3137,14 @@ class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
if getattr(panel, "field_name", None) == "advertwithcustomprimarykey"
][0]
field_html = snippet_chooser_panel.render_as_field()
field_html = snippet_chooser_panel.render_html()
self.assertIn("Choose advert with custom primary key", field_html)
self.assertIn("Choose another advert with custom primary key", field_html)
def test_render_js(self):
self.assertIn(
'new SnippetChooser("id_advertwithcustomprimarykey");',
self.snippet_chooser_panel.render_as_field(),
self.snippet_chooser_panel.render_html(),
)
def test_target_model_autodetected(self):

Wyświetl plik

@ -1084,7 +1084,7 @@ StandardChild.edit_handler = TabbedInterface(
),
ObjectList(
[
HelpPanel("remember to check for asteroids"),
HelpPanel("Watch out for asteroids"),
],
heading="Dinosaurs",
),