Enforce the use of a single string formatting mechanism for translation source strings

Close #9377
pull/9723/head
Loic Teixeira 2022-11-26 10:00:37 +01:00
rodzic 713636a00e
commit 5c1c2c8f53
60 zmienionych plików z 550 dodań i 337 usunięć

Wyświetl plik

@ -22,6 +22,7 @@ jobs:
- run: pipenv run flake8
- run: pipenv run isort --check-only --diff .
- run: pipenv run black --target-version py37 --check --diff .
- run: pipenv run semgrep --config .semgrep.yml --error .
- run: git ls-files '*.html' | xargs pipenv run djhtml --check
- run: pipenv run curlylint --parse-only wagtail
- run: pipenv run doc8 docs

Wyświetl plik

@ -50,3 +50,8 @@ repos:
rev: v1.4.13
hooks:
- id: djhtml
- repo: https://github.com/returntocorp/semgrep
rev: v0.117.0
hooks:
- id: semgrep
args: ['--config', '.semgrep.yml', '--error']

79
.semgrep.yml 100644
Wyświetl plik

@ -0,0 +1,79 @@
rules:
- id: translation-no-new-style-formatting
patterns:
- pattern: $FUNC("$STRING_ID", ...)
- metavariable-regex:
metavariable: $FUNC
regex: '_|gettext|gettext_lazy|ngettext|ngettext_lazy'
- metavariable-regex:
metavariable: $STRING_ID
regex: ".*({(\\d*|[\\w_]*)}).*"
message: |
Do not use str.format style formatting for translations.
Use printf style formatting with named placeholders instead.
For example, do `_("Hello %(name)s") % {"name": "Wagtail"}`
instead of `_("Hello {name}").format(name="Wagtail")`.
See https://docs.wagtail.org/en/latest/contributing/translations.html#marking-strings-for-translation for more information.
languages: [python, javascript, typescript]
severity: ERROR
- id: translation-no-f-strings
patterns:
- pattern: $FUNC(f"...", ...)
- metavariable-regex:
metavariable: $FUNC
regex: '_|gettext|gettext_lazy|ngettext|ngettext_lazy'
message: >
Do not use formatted string literals for translations.
Use printf style formatting with named placeholders instead.
For example, do `_("Hello %(name)s") % {"name": "Wagtail"}`
instead of `_(f"Hello {name}")`.
See https://docs.wagtail.org/en/latest/contributing/translations.html#marking-strings-for-translation for more information.
languages: [python]
severity: ERROR
- id: translation-no-anonymous-arguments
patterns:
- pattern: $FUNC("$STRING_ID", ...)
- metavariable-regex:
metavariable: $FUNC
regex: '_|gettext|gettext_lazy|ngettext|ngettext_lazy'
- metavariable-regex:
metavariable: $STRING_ID
regex: ".*%\\w.*"
message: >
Do not use anonymous placeholders for translations.
Use printf style formatting with named placeholders instead.
For example, do `_("Hello %(name)s") % {"name": "Wagtail"}`
instead of `_("Hello %s") % "Wagtail"`.
See https://docs.wagtail.org/en/latest/contributing/translations.html#marking-strings-for-translation for more information.
languages: [python, javascript, typescript]
severity: ERROR
- id: translation-no-format-within-gettext-python
patterns:
- pattern: $FUNC("..." % ..., ...)
- metavariable-regex:
metavariable: $FUNC
regex: '_|gettext|gettext_lazy|ngettext|ngettext_lazy'
message: >
Do not format string before translations
or the interpolated value will be part of the key.
Instead, interpolate after the call to gettext.
For example, do `_("Hello %(name)s") % {"name": "Wagtail"}`
instead of `_("Hello %(name)s" % {"name": "Wagtail"} )`.
See https://docs.wagtail.org/en/latest/contributing/translations.html#marking-strings-for-translation for more information.
languages: [python]
severity: ERROR
- id: translation-no-format-within-gettext-javascript
patterns:
- pattern: $FUNC("...".replace(...), ...)
- metavariable-regex:
metavariable: $FUNC
regex: '_|gettext|gettext_lazy|ngettext|ngettext_lazy'
message: >
Do not format string before translations
or the interpolated value will be part of the key.
Instead, interpolate after the call to gettext.
For example, do `_("Hello %(name)s") % {"name": "Wagtail"}`
instead of `_("Hello %(name)s" % {"name": "Wagtail"} )`.
See https://docs.wagtail.org/en/latest/contributing/translations.html#marking-strings-for-translation for more information.
languages: [javascript, typescript]
severity: ERROR

Wyświetl plik

@ -21,6 +21,7 @@ lint-server:
black --target-version py37 --check --diff .
flake8
isort --check-only --diff .
semgrep --config .semgrep.yml --error .
curlylint --parse-only wagtail
git ls-files '*.html' | xargs djhtml --check

Wyświetl plik

@ -36,10 +36,10 @@ const MinimapItem: React.FunctionComponent<MinimapItemProps> = ({
const { href, label, icon, required, errorCount, level } = item;
const hasError = errorCount > 0;
const errorsLabel = ngettext(
'{num} error',
'{num} errors',
'%(num)s error',
'%(num)s errors',
errorCount,
).replace('{num}', `${errorCount}`);
).replace('%(num)s', `${errorCount}`);
const text = label.length > 26 ? `${label.substring(0, 26)}` : label;
return (
<a

Wyświetl plik

@ -59,7 +59,7 @@ const PageExplorerItem: React.FunctionComponent<PageExplorerItemProps> = ({
>
<Icon
name="edit"
title={gettext("Edit '{title}'").replace('{title}', title || '')}
title={gettext("Edit '%(title)s'").replace('%(title)s', title || '')}
className="icon--item-action"
/>
</Button>
@ -72,8 +72,8 @@ const PageExplorerItem: React.FunctionComponent<PageExplorerItemProps> = ({
>
<Icon
name="arrow-right"
title={gettext("View child pages of '{title}'").replace(
'{title}',
title={gettext("View child pages of '%(title)s'").replace(
'%(title)s',
title || '',
)}
className="icon--item-action"

Wyświetl plik

@ -97,8 +97,8 @@ export const SubMenuItem: React.FunctionComponent<SubMenuItemProps> = ({
<span className="w-sr-only">
{dismissibleCount === 1
? gettext('(1 new item in this menu)')
: gettext('({number} new items in this menu)').replace(
'{number}',
: gettext('(%(number)s new items in this menu)').replace(
'%(number)s',
`${dismissibleCount}`,
)}
</span>

Wyświetl plik

@ -70,10 +70,10 @@ function initErrorDetection() {
.find('[data-tabs-errors-statement]')
.text(
ngettext(
'({errorCount} error)',
'({errorCount} errors)',
'(%(errorCount)s error)',
'(%(errorCount)s errors)',
errorCount,
).replace('{errorCount}', errorCount),
).replace('%(errorCount)s', errorCount),
);
});
}

Wyświetl plik

@ -166,12 +166,12 @@ function onSelectIndividualCheckbox(e) {
numObjectsSelected = getStringForListing('SINGULAR');
} else if (numCheckedObjects === checkedState.numObjects) {
numObjectsSelected = getStringForListing('ALL').replace(
'{0}',
'%(objects)s',
numCheckedObjects,
);
} else {
numObjectsSelected = getStringForListing('PLURAL').replace(
'{0}',
'%(objects)s',
numCheckedObjects,
);
}

Wyświetl plik

@ -133,10 +133,11 @@ export default function initSidePanel() {
parseInt(Math.max(minWidth, Math.min(targetWidth, maxWidth)), 10) ||
width;
const valueText = ngettext('{num} pixel', '{num} pixels', newWidth).replace(
'{num}',
const valueText = ngettext(
'%(num)s pixel',
'%(num)s pixels',
newWidth,
);
).replace('%(num)s', newWidth);
sidePanelWrapper.style.width = `${newWidth}px`;
widthInput.value = 100 - ((newWidth - minWidth) / range) * 100;

Wyświetl plik

@ -2,7 +2,7 @@
## Language
British English is preferred for user-facing text; this text should also be marked for translation (using the `django.utils.translation.gettext` function and `{% trans %}` template tag, for example). However, identifiers within code should use American English if the British or international spelling would conflict with built-in language keywords; for example, CSS code should consistently use the spelling `color` to avoid inconsistencies like `background-color: $colour-red`.
British English is preferred for user-facing text; this text should also be marked for translation (using the `django.utils.translation.gettext` function and `{% translate %}` template tag, for example). However, identifiers within code should use American English if the British or international spelling would conflict with built-in language keywords; for example, CSS code should consistently use the spelling `color` to avoid inconsistencies like `background-color: $colour-red`.
### Latin phrases and abbreviations

Wyświetl plik

@ -36,6 +36,38 @@ These new translations are imported into Wagtail for any subsequent RC and the f
- To translate a project, select it and enter your translation in the translation panel
- Save the translation using the translation button on the panel
## Marking strings for translation
In code, strings can be marked for translation with using Django's [translation system](django:topics/i18n/translation), using `gettext` or `gettext_lazy` in Python and `blocktranslate` and `translate` in templates.
In both Python and templates, make sure to always use named placeholder. In addition, in Python, only use the printf style formatting. This is to ensure compatibility with Transifex and help translators in their work.
For example:
```python
from django.utils.translation import gettext_lazy as _
# Do this: printf style + named placeholders
_("Page %(page_title)s with status %(status)s") % {"page_title": page.title, "status": page.status_string}
# Do not use anonymous placeholders
_("Page %s with status %s") % (page.title, page.status_string)
_("Page {} with status {}").format(page.title, page.status_string)
# Do not use positional placeholders
_("Page {0} with status {1}").format(page.title, page.status_string)
# Do not use new style
_("Page {page_title} with status {status}").format(page_title=page.title, status=page.status_string)
# Do not interpolate within the gettext call
_("Page %(page_title)s with status %(status)s" % {"page_title": page.title, "status": page.status_string})
_("Page {page_title} with status {status}".format(page_title=page.title, status=page.status_string))
# Do not use f-string
_(f"Page {page.title} with status {page.status_string}")
```
## Additional resources
- [](django:topics/i18n/translation)

Wyświetl plik

@ -46,4 +46,4 @@ We use [Prettier](https://prettier.io/) for formatting and [ESLint](https://esli
This is an area of active improvement for Wagtail, with [ongoing discussions](https://github.com/wagtail/wagtail/discussions/8017).
- Always use the `trimmed` attribute on `blocktrans` tags to prevent unnecessary whitespace from being added to the translation strings.
- Always use the `trimmed` attribute on `blocktranslate` tags to prevent unnecessary whitespace from being added to the translation strings.

Wyświetl plik

@ -44,7 +44,7 @@ An example of a confirmation template is as follows:
{% extends 'wagtailadmin/bulk_actions/confirmation/base.html' %}
{% load i18n wagtailadmin_tags %}
{% block titletag %}{% blocktrans trimmed count counter=items|length %}Delete 1 item{% plural %}Delete {{ counter }} items{% endblocktrans %}{% endblock %}
{% block titletag %}{% blocktranslate trimmed count counter=items|length %}Delete 1 item{% plural %}Delete {{ counter }} items{% endblocktranslate %}{% endblock %}
{% block header %}
{% trans "Delete" as del_str %}
@ -66,7 +66,7 @@ An example of a confirmation template is as follows:
{% block items_with_no_access %}
{% blocktrans trimmed asvar no_access_msg count counter=items_with_no_access|length %}You don't have permission to delete this item{% plural %}You don't have permission to delete these items{% endblocktrans %}
{% blocktranslate trimmed asvar no_access_msg count counter=items_with_no_access|length %}You don't have permission to delete this item{% plural %}You don't have permission to delete these items{% endblocktranslate %}
{% include './list_items_with_no_access.html' with items=items_with_no_access no_access_msg=no_access_msg %}
{% endblock items_with_no_access %}

Wyświetl plik

@ -60,6 +60,7 @@ testing_extras = [
"flake8-print==5.0.0",
"doc8==0.8.1",
"flake8-assertive==2.0.0",
"semgrep",
# For templates linting
"curlylint==0.13.1",
# For template indenting

Wyświetl plik

@ -122,13 +122,15 @@ class SubmitForModerationMenuItem(ActionMenuItem):
workflow_state
and workflow_state.status == workflow_state.STATUS_NEEDS_CHANGES
):
context["label"] = _("Resubmit to {}").format(
workflow_state.current_task_state.task.name
)
context["label"] = _("Resubmit to %(task_name)s") % {
"task_name": workflow_state.current_task_state.task.name
}
elif page:
workflow = page.get_workflow()
if workflow:
context["label"] = _("Submit to {}").format(workflow.name)
context["label"] = _("Submit to %(workflow_name)s") % {
"workflow_name": workflow.name
}
return context

Wyświetl plik

@ -20,9 +20,9 @@ class LoginForm(AuthenticationForm):
def __init__(self, request=None, *args, **kwargs):
super().__init__(request=request, *args, **kwargs)
self.fields["username"].widget.attrs["placeholder"] = (
gettext_lazy("Enter your %s") % self.username_field.verbose_name
)
self.fields["username"].widget.attrs["placeholder"] = gettext_lazy(
"Enter your %(username_field_name)s"
) % {"username_field_name": self.username_field.verbose_name}
self.fields["username"].widget.attrs["autofocus"] = ""
@property

Wyświetl plik

@ -101,9 +101,9 @@ class CopyForm(forms.Form):
self._errors["new_slug"] = self.error_class(
[
_(
'This slug is already in use within the context of its parent page "%s"'
'This slug is already in use within the context of its parent page "%(parent_page_title)s"'
)
% parent_page
% {"parent_page_title": parent_page}
]
)
# The slug is no longer valid, hence remove it from cleaned_data

Wyświetl plik

@ -45,13 +45,11 @@ class TagField(TaggitTagField):
value_too_long += val
if value_too_long:
raise ValidationError(
_(
"Tag(s) %(value_too_long)s are over %(max_tag_length)d characters"
% {
"value_too_long": value_too_long,
"max_tag_length": max_tag_length,
}
)
_("Tag(s) %(value_too_long)s are over %(max_tag_length)d characters")
% {
"value_too_long": value_too_long,
"max_tag_length": max_tag_length,
}
)
if not self.free_tagging:

Wyświetl plik

@ -96,9 +96,10 @@ class WorkflowPageForm(forms.ModelForm):
self.add_error(
"page",
ValidationError(
_("This page already has workflow '{0}' assigned.").format(
existing_workflow
),
_(
"This page already has workflow '%(workflow_name)s' assigned."
)
% {"workflow_name": existing_workflow},
code="existing_workflow",
),
)

Wyświetl plik

@ -57,38 +57,38 @@ def get_js_translation_strings():
"BULK_ACTIONS": {
"PAGE": {
"SINGULAR": _("1 page selected"),
"PLURAL": _("{0} pages selected"),
"ALL": _("All {0} pages on this screen selected"),
"PLURAL": _("%(objects)s pages selected"),
"ALL": _("All %(objects)s pages on this screen selected"),
"ALL_IN_LISTING": _("All pages in listing selected"),
},
"DOCUMENT": {
"SINGULAR": _("1 document selected"),
"PLURAL": _("{0} documents selected"),
"ALL": _("All {0} documents on this screen selected"),
"PLURAL": _("%(objects)s documents selected"),
"ALL": _("All %(objects)s documents on this screen selected"),
"ALL_IN_LISTING": _("All documents in listing selected"),
},
"IMAGE": {
"SINGULAR": _("1 image selected"),
"PLURAL": _("{0} images selected"),
"ALL": _("All {0} images on this screen selected"),
"PLURAL": _("%(objects)s images selected"),
"ALL": _("All %(objects)s images on this screen selected"),
"ALL_IN_LISTING": _("All images in listing selected"),
},
"USER": {
"SINGULAR": _("1 user selected"),
"PLURAL": _("{0} users selected"),
"ALL": _("All {0} users on this screen selected"),
"PLURAL": _("%(objects)s users selected"),
"ALL": _("All %(objects)s users on this screen selected"),
"ALL_IN_LISTING": _("All users in listing selected"),
},
"SNIPPET": {
"SINGULAR": _("1 snippet selected"),
"PLURAL": _("{0} snippets selected"),
"ALL": _("All {0} snippets on this screen selected"),
"PLURAL": _("%(objects)s snippets selected"),
"ALL": _("All %(objects)s snippets on this screen selected"),
"ALL_IN_LISTING": _("All snippets in listing selected"),
},
"ITEM": {
"SINGULAR": _("1 item selected"),
"PLURAL": _("{0} items selected"),
"ALL": _("All {0} items on this screen selected"),
"PLURAL": _("%(objects)s items selected"),
"ALL": _("All %(objects)s items on this screen selected"),
"ALL_IN_LISTING": _("All items in listing selected"),
},
},

Wyświetl plik

@ -737,7 +737,7 @@ def timesince_last_update(
"user_display_name": user_display_name,
}
else:
return _("%(time)s") % {"time": time_str}
return time_str
else:
if use_shorthand:
# Note: Duplicate code in timesince_simple()

Wyświetl plik

@ -31,7 +31,7 @@ class Create(CreateView):
permission_policy = collection_permission_policy
form_class = CollectionForm
page_title = gettext_lazy("Add collection")
success_message = gettext_lazy("Collection '{0}' created.")
success_message = gettext_lazy("Collection '%(object)s' created.")
add_url_name = "wagtailadmin_collections:add"
edit_url_name = "wagtailadmin_collections:edit"
index_url_name = "wagtailadmin_collections:index"
@ -58,7 +58,7 @@ class Edit(EditView):
model = Collection
form_class = CollectionForm
template_name = "wagtailadmin/collections/edit.html"
success_message = gettext_lazy("Collection '{0}' updated.")
success_message = gettext_lazy("Collection '%(object)s' updated.")
error_message = gettext_lazy("The collection could not be saved due to errors.")
delete_item_label = gettext_lazy("Delete collection")
edit_url_name = "wagtailadmin_collections:edit"
@ -134,7 +134,7 @@ class Edit(EditView):
class Delete(DeleteView):
permission_policy = collection_permission_policy
model = Collection
success_message = gettext_lazy("Collection '{0}' deleted.")
success_message = gettext_lazy("Collection '%(object)s' deleted.")
index_url_name = "wagtailadmin_collections:index"
delete_url_name = "wagtailadmin_collections:delete"
page_title = gettext_lazy("Delete collection")

Wyświetl plik

@ -208,23 +208,23 @@ class RevisionsRevertMixin:
def get_success_message(self):
message = _(
"{model_name} '{instance}' has been replaced with version from {timestamp}."
"%(model_name)s '%(object)s' has been replaced with version from %(timestamp)s."
)
if self.draftstate_enabled and self.action == "publish":
message = _(
"Version from {timestamp} of {model_name} '{instance}' has been published."
"Version from %(timestamp)s of %(model_name)s '%(object)s' has been published."
)
if self.object.go_live_at and self.object.go_live_at > timezone.now():
message = _(
"Version from {timestamp} of {model_name} '{instance}' has been scheduled for publishing."
"Version from %(timestamp)s of %(model_name)s '%(object)s' has been scheduled for publishing."
)
return message.format(
model_name=capfirst(self.model._meta.verbose_name),
instance=self.object,
timestamp=self.revision.created_at.strftime("%d %b %Y %H:%M"),
)
return message % {
"model_name": capfirst(self.model._meta.verbose_name),
"object": self.object,
"timestamp": self.revision.created_at.strftime("%d %b %Y %H:%M"),
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

Wyświetl plik

@ -403,12 +403,14 @@ class CreateView(
def get_success_message(self, instance):
if isinstance(instance, DraftStateMixin) and self.action == "publish":
if instance.go_live_at and instance.go_live_at > timezone.now():
return _("'{0}' created and scheduled for publishing.").format(instance)
return _("'{0}' created and published.").format(instance)
return _("'%(object)s' created and scheduled for publishing.") % {
"object": instance
}
return _("'%(object)s' created and published.") % {"object": instance}
if self.success_message is None:
return None
return self.success_message.format(instance)
return self.success_message % {"object": instance}
def get_success_buttons(self):
return [
@ -630,14 +632,14 @@ class EditView(
def get_success_message(self):
if self.draftstate_enabled and self.action == "publish":
if self.object.go_live_at and self.object.go_live_at > timezone.now():
return _("'{0}' updated and scheduled for publishing.").format(
self.object
)
return _("'{0}' updated and published.").format(self.object)
return _("'%(object)s' updated and scheduled for publishing.") % {
"object": self.object
}
return _("'%(object)s' updated and published.") % {"object": self.object}
if self.success_message is None:
return None
return self.success_message.format(self.object)
return self.success_message % {"object": self.object}
def get_success_buttons(self):
return [
@ -761,7 +763,7 @@ class DeleteView(
def get_success_message(self):
if self.success_message is None:
return None
return self.success_message.format(self.object)
return self.success_message % {"object": self.object}
def delete_action(self):
with transaction.atomic():
@ -883,7 +885,7 @@ class UnpublishView(HookResponseMixin, TemplateView):
index_url_name = None
edit_url_name = None
unpublish_url_name = None
success_message = _("'{object_name}' unpublished.")
success_message = _("'%(object)s' unpublished.")
template_name = "wagtailadmin/shared/confirm_unpublish.html"
def setup(self, request, pk, *args, **kwargs):
@ -910,7 +912,7 @@ class UnpublishView(HookResponseMixin, TemplateView):
def get_success_message(self):
if self.success_message is None:
return None
return self.success_message.format(object_name=str(self.object))
return self.success_message % {"object": str(self.object)}
def get_success_buttons(self):
if self.edit_url_name:
@ -977,7 +979,9 @@ class RevisionsUnscheduleView(TemplateView):
edit_url_name = None
history_url_name = None
revisions_unschedule_url_name = None
success_message = gettext_lazy('Version {revision_id} of "{object}" unscheduled.')
success_message = gettext_lazy(
'Version %(revision_id)s of "%(object)s" unscheduled.'
)
template_name = "wagtailadmin/shared/revisions/confirm_unschedule.html"
def setup(self, request, pk, revision_id, *args, **kwargs):
@ -1007,9 +1011,10 @@ class RevisionsUnscheduleView(TemplateView):
def get_success_message(self):
if self.success_message is None:
return None
return self.success_message.format(
revision_id=self.revision.id, object=self.get_object_display_title()
)
return self.success_message % {
"revision_id": self.revision.id,
"object": self.get_object_display_title(),
}
def get_success_buttons(self):
return [
@ -1031,9 +1036,10 @@ class RevisionsUnscheduleView(TemplateView):
return reverse(self.history_url_name, args=(quote(self.object.pk),))
def get_page_subtitle(self):
return _('revision {revision_id} of "{object}"').format(
revision_id=self.revision.id, object=self.get_object_display_title()
)
return _('revision %(revision_id)s of "%(object)s"') % {
"revision_id": self.revision.id,
"object": self.get_object_display_title(),
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

Wyświetl plik

@ -30,9 +30,8 @@ def convert_alias(request, page_id):
messages.success(
request,
_("Page '{0}' has been converted into an ordinary page.").format(
page.get_admin_display_title()
),
_("Page '%(page_title)s' has been converted into an ordinary page.")
% {"page_title": page.get_admin_display_title()},
)
for fn in hooks.get_hooks("after_convert_alias_page"):

Wyświetl plik

@ -81,17 +81,17 @@ def copy(request, page_id):
if form.cleaned_data.get("copy_subpages"):
messages.success(
request,
_("Page '{0}' and {1} subpages copied.").format(
page.specific_deferred.get_admin_display_title(),
new_page.get_descendants().count(),
),
_("Page '%(page_title)s' and %(subpages_count)s subpages copied.")
% {
"page_title": page.specific_deferred.get_admin_display_title(),
"subpages_count": new_page.get_descendants().count(),
},
)
else:
messages.success(
request,
_("Page '{0}' copied.").format(
page.specific_deferred.get_admin_display_title()
),
_("Page '%(page_title)s' copied.")
% {"page_title": page.specific_deferred.get_admin_display_title()},
)
for fn in hooks.get_hooks("after_copy_page"):

Wyświetl plik

@ -179,7 +179,8 @@ class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
# Notification
messages.success(
self.request,
_("Page '{0}' created.").format(self.page.get_admin_display_title()),
_("Page '%(page_title)s' created.")
% {"page_title": self.page.get_admin_display_title()},
)
response = self.run_hook("after_create_page", self.request, self.page)
@ -220,9 +221,8 @@ class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
if self.page.go_live_at and self.page.go_live_at > timezone.now():
messages.success(
self.request,
_("Page '{0}' created and scheduled for publishing.").format(
self.page.get_admin_display_title()
),
_("Page '%(page_title)s' created and scheduled for publishing.")
% {"page_title": self.page.get_admin_display_title()},
buttons=[self.get_edit_message_button()],
)
else:
@ -232,9 +232,8 @@ class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
buttons.append(self.get_edit_message_button())
messages.success(
self.request,
_("Page '{0}' created and published.").format(
self.page.get_admin_display_title()
),
_("Page '%(page_title)s' created and published.")
% {"page_title": self.page.get_admin_display_title()},
buttons=buttons,
)
@ -271,9 +270,8 @@ class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
messages.success(
self.request,
_("Page '{0}' created and submitted for moderation.").format(
self.page.get_admin_display_title()
),
_("Page '%(page_title)s' created and submitted for moderation.")
% {"page_title": self.page.get_admin_display_title()},
buttons=buttons,
)

Wyświetl plik

@ -67,7 +67,8 @@ def delete(request, page_id):
messages.success(
request,
_("Page '{0}' deleted.").format(page.get_admin_display_title()),
_("Page '%(page_title)s' deleted.")
% {"page_title": page.get_admin_display_title()},
)
for fn in hooks.get_hooks("after_delete_page"):

Wyświetl plik

@ -57,14 +57,19 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
def add_save_confirmation_message(self):
if self.is_reverting:
message = _("Page '{0}' has been replaced with version from {1}.").format(
self.page.get_admin_display_title(),
self.previous_revision.created_at.strftime("%d %b %Y %H:%M"),
)
message = _(
"Page '%(page_title)s' has been replaced "
"with version from %(previous_revision_datetime)s."
) % {
"page_title": self.page.get_admin_display_title(),
"previous_revision_datetime": self.previous_revision.created_at.strftime(
"%d %b %Y %H:%M"
),
}
else:
message = _("Page '{0}' has been updated.").format(
self.page.get_admin_display_title()
)
message = _("Page '%(page_title)s' has been updated.") % {
"page_title": self.page.get_admin_display_title()
}
messages.success(self.request, message)
@ -424,9 +429,9 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
return self.render_to_response(self.get_context_data())
def add_cancel_workflow_confirmation_message(self):
message = _("Workflow on page '{0}' has been cancelled.").format(
self.page.get_admin_display_title()
)
message = _("Workflow on page '%(page_title)s' has been cancelled.") % {
"page_title": self.page.get_admin_display_title()
}
messages.success(
self.request,
@ -578,21 +583,24 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
if self.is_reverting:
message = _(
"Version from {0} of page '{1}' has been scheduled for publishing."
).format(
self.previous_revision.created_at.strftime("%d %b %Y %H:%M"),
self.page.get_admin_display_title(),
)
"Version from %(previous_revision_datetime)s "
"of page '%(page_title)s' has been scheduled for publishing."
) % {
"previous_revision_datetime": self.previous_revision.created_at.strftime(
"%d %b %Y %H:%M"
),
"page_title": self.page.get_admin_display_title(),
}
else:
if self.page.live:
message = _(
"Page '{0}' is live and this version has been scheduled for publishing."
).format(self.page.get_admin_display_title())
"Page '%(page_title)s' is live and this version has been scheduled for publishing."
) % {"page_title": self.page.get_admin_display_title()}
else:
message = _("Page '{0}' has been scheduled for publishing.").format(
self.page.get_admin_display_title()
)
message = _(
"Page '%(page_title)s' has been scheduled for publishing."
) % {"page_title": self.page.get_admin_display_title()}
messages.success(
self.request, message, buttons=[self.get_edit_message_button()]
@ -603,15 +611,17 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
if self.is_reverting:
message = _(
"Version from {0} of page '{1}' has been published."
).format(
self.previous_revision.created_at.strftime("%d %b %Y %H:%M"),
self.page.get_admin_display_title(),
)
"Version from %(datetime)s of page '%(page_title)s' has been published."
) % {
"datetime": self.previous_revision.created_at.strftime(
"%d %b %Y %H:%M"
),
"page_title": self.page.get_admin_display_title(),
}
else:
message = _("Page '{0}' has been published.").format(
self.page.get_admin_display_title()
)
message = _("Page '%(page_title)s' has been published.") % {
"page_title": self.page.get_admin_display_title()
}
buttons = []
if self.page.url is not None:
@ -653,9 +663,9 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
workflow = self.page.get_workflow()
workflow.start(self.page, self.request.user)
message = _("Page '{0}' has been submitted for moderation.").format(
self.page.get_admin_display_title()
)
message = _("Page '%(page_title)s' has been submitted for moderation.") % {
"page_title": self.page.get_admin_display_title()
}
messages.success(
self.request,
@ -695,9 +705,9 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View):
workflow = self.page.get_workflow()
workflow.start(self.page, self.request.user)
message = _("Workflow on page '{0}' has been restarted.").format(
self.page.get_admin_display_title()
)
message = _("Workflow on page '%(page_title)s' has been restarted.") % {
"page_title": self.page.get_admin_display_title()
}
messages.success(
self.request,

Wyświetl plik

@ -52,7 +52,8 @@ def unlock(request, page_id):
messages.success(
request,
_("Page '{0}' is now unlocked.").format(page.get_admin_display_title()),
_("Page '%(page_title)s' is now unlocked.")
% {"page_title": page.get_admin_display_title()},
extra_tags="unlock",
)

Wyświetl plik

@ -17,18 +17,19 @@ def approve_moderation(request, revision_id):
if not revision.submitted_for_moderation:
messages.error(
request,
_("The page '{0}' is not currently awaiting moderation.").format(
revision.content_object.specific_deferred.get_admin_display_title()
),
_("The page '%(page_title)s' is not currently awaiting moderation.")
% {
"page_title": revision.content_object.specific_deferred.get_admin_display_title()
},
)
return redirect("wagtailadmin_home")
if request.method == "POST":
revision.approve_moderation(user=request.user)
message = _("Page '{0}' published.").format(
revision.content_object.specific_deferred.get_admin_display_title()
)
message = _("Page '%(page_title)s' published.") % {
"page_title": revision.content_object.specific_deferred.get_admin_display_title()
}
buttons = []
if revision.content_object.url is not None:
buttons.append(
@ -58,9 +59,10 @@ def reject_moderation(request, revision_id):
if not revision.submitted_for_moderation:
messages.error(
request,
_("The page '{0}' is not currently awaiting moderation.").format(
revision.content_object.specific_deferred.get_admin_display_title()
),
_("The page '%(page_title)s' is not currently awaiting moderation.")
% {
"page_title": revision.content_object.specific_deferred.get_admin_display_title()
},
)
return redirect("wagtailadmin_home")
@ -69,9 +71,10 @@ def reject_moderation(request, revision_id):
messages.success(
request,
_("Page '{0}' rejected for publication.").format(
revision.content_object.specific_deferred.get_admin_display_title()
),
_("Page '%(page_title)s' rejected for publication.")
% {
"page_title": revision.content_object.specific_deferred.get_admin_display_title()
},
buttons=[
messages.button(
reverse(
@ -97,9 +100,10 @@ def preview_for_moderation(request, revision_id):
if not revision.submitted_for_moderation:
messages.error(
request,
_("The page '{0}' is not currently awaiting moderation.").format(
revision.content_object.specific_deferred.get_admin_display_title()
),
_("The page '%(page_title)s' is not currently awaiting moderation.")
% {
"page_title": revision.content_object.specific_deferred.get_admin_display_title()
},
)
return redirect("wagtailadmin_home")

Wyświetl plik

@ -56,8 +56,9 @@ def move_confirm(request, page_to_move_id, destination_id):
messages.error(
request,
_(
"The slug '{0}' is already in use at the selected parent page. Make sure the slug is unique and try again"
).format(page_to_move.slug),
"The slug '%(page_slug)s' is already in use at the selected parent page. Make sure the slug is unique and try again"
)
% {"page_slug": page_to_move.slug},
)
return redirect(
"wagtailadmin_pages:move",
@ -107,7 +108,8 @@ def move_confirm(request, page_to_move_id, destination_id):
messages.success(
request,
_("Page '{0}' moved.").format(page_to_move.get_admin_display_title()),
_("Page '%(page_title)s' moved.")
% {"page_title": page_to_move.get_admin_display_title()},
buttons=[
messages.button(
reverse("wagtailadmin_pages:edit", args=(page_to_move.id,)),

Wyświetl plik

@ -16,7 +16,7 @@ class Unpublish(UnpublishView):
index_url_name = "wagtailadmin_explore"
edit_url_name = "wagtailadmin_pages:edit"
unpublish_url_name = "wagtailadmin_pages:unpublish"
success_message = _("Page '{0}' unpublished.")
success_message = _("Page '%(page_title)s' unpublished.")
template_name = "wagtailadmin/pages/confirm_unpublish.html"
def setup(self, request, page_id, *args, **kwargs):
@ -36,7 +36,9 @@ class Unpublish(UnpublishView):
return super().dispatch(request, *args, **kwargs)
def get_success_message(self):
return self.success_message.format(self.object.get_admin_display_title())
return self.success_message % {
"page_title": self.object.get_admin_display_title()
}
def get_next_url(self):
next_url = get_valid_next_url_from_request(self.request)

Wyświetl plik

@ -38,9 +38,8 @@ class BaseWorkflowFormView(View):
if not self.page.workflow_in_progress:
messages.error(
request,
_("The page '{0}' is not currently awaiting moderation.").format(
self.page.get_admin_display_title()
),
_("The page '%(page_title)s' is not currently awaiting moderation.")
% {"page_title": self.page.get_admin_display_title()},
)
return redirect(self.redirect_to)
@ -250,8 +249,9 @@ def preview_revision_for_task(request, page_id, task_id):
messages.error(
request,
_(
"The page '{0}' is not currently awaiting moderation in task '{1}'."
).format(page.get_admin_display_title(), task.name),
"The page '%(page_title)s' is not currently awaiting moderation in task '%(task_name)s'."
)
% {"page_title": page.get_admin_display_title(), "task_name": task.name},
)
return redirect("wagtailadmin_home")

Wyświetl plik

@ -71,7 +71,7 @@ class Create(CreateView):
model = Workflow
page_title = _("New workflow")
template_name = "wagtailadmin/workflows/create.html"
success_message = _("Workflow '{0}' created.")
success_message = _("Workflow '%(object)s' created.")
add_url_name = "wagtailadmin_workflows:add"
edit_url_name = "wagtailadmin_workflows:edit"
index_url_name = "wagtailadmin_workflows:index"
@ -142,7 +142,7 @@ class Edit(EditView):
model = Workflow
page_title = _("Editing workflow")
template_name = "wagtailadmin/workflows/edit.html"
success_message = _("Workflow '{0}' updated.")
success_message = _("Workflow '%(object)s' updated.")
add_url_name = "wagtailadmin_workflows:add"
edit_url_name = "wagtailadmin_workflows:edit"
delete_url_name = "wagtailadmin_workflows:disable"
@ -244,7 +244,7 @@ class Disable(DeleteView):
model = Workflow
page_title = _("Disable workflow")
template_name = "wagtailadmin/workflows/confirm_disable.html"
success_message = _("Workflow '{0}' disabled.")
success_message = _("Workflow '%(object)s' disabled.")
add_url_name = "wagtailadmin_workflows:add"
edit_url_name = "wagtailadmin_workflows:edit"
delete_url_name = "wagtailadmin_workflows:disable"
@ -305,7 +305,11 @@ def enable_workflow(request, pk):
if not workflow.active:
workflow.active = True
workflow.save()
messages.success(request, _("Workflow '{0}' enabled.").format(workflow.name))
messages.success(
request,
_("Workflow '%(workflow_name)s' enabled.")
% {"workflow_name": workflow.name},
)
# Redirect
redirect_to = request.POST.get("next", None)
@ -334,9 +338,8 @@ def remove_workflow(request, page_pk, workflow_pk=None):
page.workflowpage.delete()
messages.success(
request,
_("Workflow removed from Page '{0}'.").format(
page.get_admin_display_title()
),
_("Workflow removed from Page '%(page_title)s'.")
% {"page_title": page.get_admin_display_title()},
)
# Redirect
@ -414,7 +417,7 @@ class CreateTask(CreateView):
model = None
page_title = _("New workflow task")
template_name = "wagtailadmin/workflows/create_task.html"
success_message = _("Task '{0}' created.")
success_message = _("Task '%(object)s' created.")
add_url_name = "wagtailadmin_workflows:add_task"
edit_url_name = "wagtailadmin_workflows:edit_task"
index_url_name = "wagtailadmin_workflows:task_index"
@ -456,7 +459,7 @@ class EditTask(EditView):
model = None
page_title = _("Editing workflow task")
template_name = "wagtailadmin/workflows/edit_task.html"
success_message = _("Task '{0}' updated.")
success_message = _("Task '%(object)s' updated.")
add_url_name = "wagtailadmin_workflows:select_task_type"
edit_url_name = "wagtailadmin_workflows:edit_task"
delete_url_name = "wagtailadmin_workflows:disable_task"
@ -510,7 +513,7 @@ class DisableTask(DeleteView):
model = Task
page_title = _("Disable task")
template_name = "wagtailadmin/workflows/confirm_disable_task.html"
success_message = _("Task '{0}' disabled.")
success_message = _("Task '%(object)s' disabled.")
add_url_name = "wagtailadmin_workflows:add_task"
edit_url_name = "wagtailadmin_workflows:edit_task"
delete_url_name = "wagtailadmin_workflows:disable_task"
@ -552,7 +555,9 @@ def enable_task(request, pk):
if not task.active:
task.active = True
task.save()
messages.success(request, _("Task '{0}' enabled.").format(task.name))
messages.success(
request, _("Task '%(task_name)s' enabled.") % {"task_name": task.name}
)
# Redirect
redirect_to = request.POST.get("next", None)

Wyświetl plik

@ -976,7 +976,7 @@ def register_reports_menu():
def register_whats_new_in_wagtail_version_menu_item():
version = "4.1"
return DismissibleMenuItem(
_("What's new in Wagtail {version}").format(version=version),
_("What's new in Wagtail %(version)s") % {"version": version},
wagtail_feature_release_whats_new_link(),
icon_name="help",
order=1000,

Wyświetl plik

@ -175,14 +175,16 @@ class ListBlock(Block):
if self.meta.min_num is not None and self.meta.min_num > len(value):
non_block_errors.append(
ValidationError(
_("The minimum number of items is %d") % self.meta.min_num
_("The minimum number of items is %(min_num)d")
% {"min_num": self.meta.min_num}
)
)
if self.meta.max_num is not None and self.meta.max_num < len(value):
non_block_errors.append(
ValidationError(
_("The maximum number of items is %d") % self.meta.max_num
_("The maximum number of items is %(max_num)d")
% {"max_num": self.meta.max_num}
)
)

Wyświetl plik

@ -159,7 +159,8 @@ class BaseStreamBlock(Block):
if self.meta.min_num is not None and self.meta.min_num > len(value):
non_block_errors.append(
ValidationError(
_("The minimum number of items is %d") % self.meta.min_num
_("The minimum number of items is %(min_num)d")
% {"min_num": self.meta.min_num}
)
)
elif self.required and len(value) == 0:
@ -168,7 +169,8 @@ class BaseStreamBlock(Block):
if self.meta.max_num is not None and self.meta.max_num < len(value):
non_block_errors.append(
ValidationError(
_("The maximum number of items is %d") % self.meta.max_num
_("The maximum number of items is %(max_num)d")
% {"max_num": self.meta.max_num}
)
)
@ -187,7 +189,8 @@ class BaseStreamBlock(Block):
ValidationError(
"{}: {}".format(
block.label,
_("The minimum number of items is %d") % min_num,
_("The minimum number of items is %(min_num)d")
% {"min_num": min_num},
)
)
)
@ -196,7 +199,8 @@ class BaseStreamBlock(Block):
ValidationError(
"{}: {}".format(
block.label,
_("The maximum number of items is %d") % max_num,
_("The maximum number of items is %(max_num)d")
% {"max_num": max_num},
)
)
)

Wyświetl plik

@ -102,15 +102,15 @@ class FormBuilder:
"""
if "\n" in field.choices:
choices = map(
lambda x: (
choices = (
(
x.strip().rstrip(",").strip(),
x.strip().rstrip(",").strip(),
),
field.choices.split("\r\n"),
)
for x in field.choices.split("\r\n")
)
else:
choices = map(lambda x: (x.strip(), x.strip()), field.choices.split(","))
choices = ((x.strip(), x.strip()) for x in field.choices.split(","))
return choices
@ -201,8 +201,8 @@ class WagtailAdminFormPageForm(WagtailAdminPageForm):
"label",
django.forms.ValidationError(
_(
"There is another field with the label %s, please change one of them."
% duplicate_form_field.instance.label
"There is another field with the label %(label_name)s, please change one of them."
)
% {"label_name": duplicate_form_field.instance.label}
),
)

Wyświetl plik

@ -7,7 +7,9 @@ from wagtail.admin.panels import Panel
class FormSubmissionsPanel(Panel):
def on_model_bound(self):
if not self.heading:
self.heading = _("%s submissions") % self.model.get_verbose_name()
self.heading = _("%(model_name)s submissions") % {
"model_name": self.model.get_verbose_name()
}
class BoundPanel(Panel.BoundPanel):
template_name = "wagtailforms/panels/form_responses_panel.html"

Wyświetl plik

@ -39,9 +39,9 @@ class ButtonHelper:
cn = self.finalise_classname(classnames, classnames_exclude)
return {
"url": self.url_helper.create_url,
"label": _("Add %s") % self.verbose_name,
"label": _("Add %(object)s") % {"object": self.verbose_name},
"classname": cn,
"title": _("Add a new %s") % self.verbose_name,
"title": _("Add a new %(object)s") % {"object": self.verbose_name},
}
def inspect_button(self, pk, classnames_add=None, classnames_exclude=None):
@ -55,7 +55,7 @@ class ButtonHelper:
"url": self.url_helper.get_action_url("inspect", quote(pk)),
"label": _("Inspect"),
"classname": cn,
"title": _("Inspect this %s") % self.verbose_name,
"title": _("Inspect this %(object)s") % {"object": self.verbose_name},
}
def edit_button(self, pk, classnames_add=None, classnames_exclude=None):
@ -69,7 +69,7 @@ class ButtonHelper:
"url": self.url_helper.get_action_url("edit", quote(pk)),
"label": _("Edit"),
"classname": cn,
"title": _("Edit this %s") % self.verbose_name,
"title": _("Edit this %(object)s") % {"object": self.verbose_name},
}
def delete_button(self, pk, classnames_add=None, classnames_exclude=None):
@ -83,7 +83,7 @@ class ButtonHelper:
"url": self.url_helper.get_action_url("delete", quote(pk)),
"label": _("Delete"),
"classname": cn,
"title": _("Delete this %s") % self.verbose_name,
"title": _("Delete this %(object)s") % {"object": self.verbose_name},
}
def get_buttons_for_obj(
@ -133,7 +133,7 @@ class PageButtonHelper(ButtonHelper):
"url": self.url_helper.get_action_url("unpublish", quote(pk)),
"label": _("Unpublish"),
"classname": cn,
"title": _("Unpublish this %s") % self.verbose_name,
"title": _("Unpublish this %(object)s") % {"object": self.verbose_name},
}
def copy_button(self, pk, classnames_add=None, classnames_exclude=None):
@ -147,7 +147,7 @@ class PageButtonHelper(ButtonHelper):
"url": self.url_helper.get_action_url("copy", quote(pk)),
"label": _("Copy"),
"classname": cn,
"title": _("Copy this %s") % self.verbose_name,
"title": _("Copy this %(object)s") % {"object": self.verbose_name},
}
def get_buttons_for_obj(

Wyświetl plik

@ -246,9 +246,9 @@ class ModelFormView(WMABaseView, FormView):
return fields
def get_success_message(self, instance):
return _("%(model_name)s '%(instance)s' created.") % {
return _("%(model_name)s '%(object)s' created.") % {
"model_name": capfirst(self.opts.verbose_name),
"instance": instance,
"object": instance,
}
def get_success_message_buttons(self, instance):
@ -257,7 +257,9 @@ class ModelFormView(WMABaseView, FormView):
def get_error_message(self):
model_name = self.verbose_name
return _("The %s could not be created due to errors.") % model_name
return _("The %(object)s could not be created due to errors.") % {
"object": model_name
}
def form_valid(self, form):
self.instance = form.save()
@ -799,7 +801,7 @@ class CreateView(ModelFormView):
return response
def get_meta_title(self):
return _("Create new %s") % self.verbose_name
return _("Create new %(object)s") % {"object": self.verbose_name}
def get_page_subtitle(self):
return capfirst(self.verbose_name)
@ -859,12 +861,12 @@ class EditView(ModelFormView, InstanceSpecificView):
return super().dispatch(request, *args, **kwargs)
def get_meta_title(self):
return _("Editing %s") % self.verbose_name
return _("Editing %(object)s") % {"object": self.verbose_name}
def get_success_message(self, instance):
return _("%(model_name)s '%(instance)s' updated.") % {
return _("%(model_name)s '%(object)s' updated.") % {
"model_name": capfirst(self.verbose_name),
"instance": instance,
"object": instance,
}
def get_context_data(self, **kwargs):
@ -889,7 +891,7 @@ class EditView(ModelFormView, InstanceSpecificView):
def get_error_message(self):
name = self.verbose_name
return _("The %s could not be saved due to errors.") % name
return _("The %(object)s could not be saved due to errors.") % {"object": name}
def get_template_names(self):
return self.model_admin.get_edit_template()
@ -923,7 +925,7 @@ class ChooseParentView(WMABaseView):
return super().dispatch(request, *args, **kwargs)
def get_page_title(self):
return _("Add %s") % self.verbose_name
return _("Add %(object)s") % {"object": self.verbose_name}
def get_form(self, request):
parents = self.permission_helper.get_valid_parent_pages(request.user)
@ -971,25 +973,22 @@ class DeleteView(InstanceSpecificView):
return super().dispatch(request, *args, **kwargs)
def get_meta_title(self):
return _("Confirm deletion of %s") % self.verbose_name
return _("Confirm deletion of %(object)s") % {"object": self.verbose_name}
def confirmation_message(self):
return (
_(
"Are you sure you want to delete this %s? If other things in your "
"site are related to it, they may also be affected."
)
% self.verbose_name
)
return _(
"Are you sure you want to delete this %(object)s? If other things in your "
"site are related to it, they may also be affected."
) % {"object": self.verbose_name}
def delete_instance(self):
self.instance.delete()
def post(self, request, *args, **kwargs):
try:
msg = _("%(model_name)s '%(instance)s' deleted.") % {
msg = _("%(model_name)s '%(object)s' deleted.") % {
"model_name": self.verbose_name,
"instance": self.instance,
"object": self.instance,
}
with transaction.atomic():
log(instance=self.instance, action="wagtail.delete")
@ -1065,7 +1064,7 @@ class InspectView(InstanceSpecificView):
)
def get_meta_title(self):
return _("Inspecting %s") % self.verbose_name
return _("Inspecting %(object)s") % {"object": self.verbose_name}
def get_field_label(self, field_name, field=None):
"""Return a label to display for a field"""

Wyświetl plik

@ -111,7 +111,8 @@ def edit(request, redirect_id):
log(instance=theredirect, action="wagtail.edit")
messages.success(
request,
_("Redirect '{0}' updated.").format(theredirect.title),
_("Redirect '%(redirect_title)s' updated.")
% {"redirect_title": theredirect.title},
buttons=[
messages.button(
reverse("wagtailredirects:edit", args=(theredirect.id,)),
@ -152,7 +153,9 @@ def delete(request, redirect_id):
log(instance=theredirect, action="wagtail.delete")
theredirect.delete()
messages.success(
request, _("Redirect '{0}' deleted.").format(theredirect.title)
request,
_("Redirect '%(redirect_title)s' deleted.")
% {"redirect_title": theredirect.title},
)
return redirect("wagtailredirects:index")
@ -176,7 +179,8 @@ def add(request):
messages.success(
request,
_("Redirect '{0}' added.").format(theredirect.title),
_("Redirect '%(redirect_title)s' added.")
% {"redirect_title": theredirect.title},
buttons=[
messages.button(
reverse("wagtailredirects:edit", args=(theredirect.id,)),
@ -239,7 +243,9 @@ def start_import(request):
if extension not in supported_extensions:
messages.error(
request, _('File format of type "{}" is not supported').format(extension)
request,
_('File format of type "%(extension)s" is not supported')
% {"extension": extension},
)
return redirect("wagtailredirects:start_import")
@ -253,7 +259,11 @@ def start_import(request):
data = force_str(data, from_encoding)
dataset = input_format.create_dataset(data)
except UnicodeDecodeError as e:
messages.error(request, _("Imported file has a wrong encoding: %s") % e)
messages.error(
request,
_("Imported file has a wrong encoding: %(error_message)s")
% {"error_message": e},
)
return redirect("wagtailredirects:start_import")
except Exception as e: # pragma: no cover
messages.error(

Wyświetl plik

@ -137,7 +137,7 @@ def add(request):
log(search_pick, "wagtail.create")
messages.success(
request,
_("Editor's picks for '{0}' created.").format(query),
_("Editor's picks for '%(query)s' created.") % {"query": query},
buttons=[
messages.button(
reverse("wagtailsearchpromotions:edit", args=(query.id,)),
@ -197,7 +197,7 @@ def edit(request, query_id):
if save_searchpicks(query, new_query, searchpicks_formset):
messages.success(
request,
_("Editor's picks for '{0}' updated.").format(new_query),
_("Editor's picks for '%(query)s' updated.") % {"query": new_query},
buttons=[
messages.button(
reverse("wagtailsearchpromotions:edit", args=(query.id,)),

Wyświetl plik

@ -42,10 +42,10 @@ class SubmitTranslationForm(forms.Form):
if descendant_count > 0:
hide_include_subtree = False
self.fields["include_subtree"].label = ngettext(
"Include subtree ({} page)",
"Include subtree ({} pages)",
"Include subtree (%(descendant_count)s page)",
"Include subtree (%(descendant_count)s pages)",
descendant_count,
).format(descendant_count)
) % {"descendant_count": descendant_count}
if hide_include_subtree:
self.fields["include_subtree"].widget = forms.HiddenInput()

Wyświetl plik

@ -74,8 +74,10 @@ class SubmitTranslationView(SingleObjectMixin, TemplateView):
form.cleaned_data["locales"][0]
)
else:
# Note: always plural
locales = _("{} locales").format(len(form.cleaned_data["locales"]))
# Translators: always plural
locales = _("%(locales_count)s locales") % {
"locales_count": len(form.cleaned_data["locales"])
}
messages.success(self.request, self.get_success_message(locales))
@ -117,15 +119,15 @@ class SubmitPageTranslationView(SubmitTranslationView):
def get_success_message(self, locales):
return _(
"The page '{page_title}' was successfully created in {locales}"
).format(page_title=self.object.get_admin_display_title(), locales=locales)
"The page '%(page_title)s' was successfully created in %(locales)s"
) % {"page_title": self.object.get_admin_display_title(), "locales": locales}
class SubmitSnippetTranslationView(SubmitTranslationView):
def get_title(self):
return _("Translate {model_name}").format(
model_name=self.object._meta.verbose_name
)
return _("Translate %(model_name)s") % {
"model_name": self.object._meta.verbose_name
}
def get_object(self):
model = get_snippet_model_from_url_params(
@ -153,8 +155,8 @@ class SubmitSnippetTranslationView(SubmitTranslationView):
)
def get_success_message(self, locales):
return _("Successfully created {locales} for {model_name} '{object}'").format(
model_name=self.object._meta.verbose_name,
object=str(self.object),
locales=locales,
)
return _("Successfully created %(locales)s for %(model_name)s '%(object)s'") % {
"model_name": self.object._meta.verbose_name,
"object": str(self.object),
"locales": locales,
}

Wyświetl plik

@ -137,7 +137,8 @@ def add(request):
messages.success(
request,
_("Document '{0}' added.").format(doc.title),
_("Document '%(document_title)s' added.")
% {"document_title": doc.title},
buttons=[
messages.button(
reverse("wagtaildocs:edit", args=(doc.id,)), _("Edit")
@ -188,7 +189,8 @@ def edit(request, document_id):
messages.success(
request,
_("Document '{0}' updated").format(doc.title),
_("Document '%(document_title)s' updated")
% {"document_title": doc.title},
buttons=[messages.button(edit_url, _("Edit"))],
)
return redirect(redirect_url)
@ -247,7 +249,10 @@ def delete(request, document_id):
if request.method == "POST":
doc.delete()
messages.success(request, _("Document '{0}' deleted.").format(doc.title))
messages.success(
request,
_("Document '%(document_title)s' deleted.") % {"document_title": doc.title},
)
return redirect(next_url) if next_url else redirect("wagtaildocs:index")
return TemplateResponse(
@ -279,11 +284,13 @@ def usage(request, document_id):
for object, references in object_page:
edit_url = url_finder.get_edit_url(object)
if edit_url is None:
label = _("(Private %s)") % object._meta.verbose_name
label = _("(Private %(object)s)") % {"object": object._meta.verbose_name}
edit_link_title = None
else:
label = str(object)
edit_link_title = _("Edit this %s") % object._meta.verbose_name
edit_link_title = _("Edit this %(object)s") % {
"object": object._meta.verbose_name
}
results.append((label, edit_url, edit_link_title, references))
return TemplateResponse(

Wyświetl plik

@ -26,7 +26,7 @@ class WagtailImageField(ImageField):
self.max_image_pixels = getattr(
settings, "WAGTAILIMAGES_MAX_IMAGE_PIXELS", 128 * 1000000
)
max_upload_size_text = filesizeformat(self.max_upload_size)
self.max_upload_size_text = filesizeformat(self.max_upload_size)
# Help text
if self.max_upload_size is not None:
@ -34,7 +34,7 @@ class WagtailImageField(ImageField):
"Supported formats: %(supported_formats)s. Maximum filesize: %(max_upload_size)s."
) % {
"supported_formats": SUPPORTED_FORMATS_TEXT,
"max_upload_size": max_upload_size_text,
"max_upload_size": self.max_upload_size_text,
}
else:
self.help_text = _("Supported formats: %(supported_formats)s.") % {
@ -42,27 +42,27 @@ class WagtailImageField(ImageField):
}
# Error messages
self.error_messages["invalid_image_extension"] = (
_("Not a supported image format. Supported formats: %s.")
% SUPPORTED_FORMATS_TEXT
)
# Translation placeholders should all be interpolated at the same time to avoid escaping,
# either right now if all values are known, otherwise when used.
self.error_messages["invalid_image_extension"] = _(
"Not a supported image format. Supported formats: %(supported_formats)s."
) % {"supported_formats": SUPPORTED_FORMATS_TEXT}
self.error_messages["invalid_image_known_format"] = _(
"Not a valid .%s image. The extension does not match the file format (%s)"
"Not a valid .%(extension)s image. The extension does not match the file format (%(image_format)s)"
)
self.error_messages["file_too_large"] = (
_("This file is too big (%%s). Maximum filesize %s.") % max_upload_size_text
self.error_messages["file_too_large"] = _(
"This file is too big (%(file_size)s). Maximum filesize %(max_filesize)s."
)
self.error_messages["file_too_many_pixels"] = (
_("This file has too many pixels (%%s). Maximum pixels %s.")
% self.max_image_pixels
self.error_messages["file_too_many_pixels"] = _(
"This file has too many pixels (%(num_pixels)s). Maximum pixels %(max_pixels_count)s."
)
self.error_messages["file_too_large_unknown_size"] = (
_("This file is too big. Maximum filesize %s.") % max_upload_size_text
)
self.error_messages["file_too_large_unknown_size"] = _(
"This file is too big. Maximum filesize %(max_filesize)s."
) % {"max_filesize": self.max_upload_size_text}
def check_image_file_format(self, f):
# Check file extension
@ -82,7 +82,7 @@ class WagtailImageField(ImageField):
if extension != f.image.format_name:
raise ValidationError(
self.error_messages["invalid_image_known_format"]
% (extension, f.image.format_name),
% {"extension": extension, "image_format": f.image.format_name},
code="invalid_image_known_format",
)
@ -94,7 +94,11 @@ class WagtailImageField(ImageField):
# Check the filesize
if f.size > self.max_upload_size:
raise ValidationError(
self.error_messages["file_too_large"] % (filesizeformat(f.size),),
self.error_messages["file_too_large"]
% {
"file_size": filesizeformat(f.size),
"max_filesize": self.max_upload_size_text,
},
code="file_too_large",
)
@ -110,7 +114,8 @@ class WagtailImageField(ImageField):
if num_pixels > self.max_image_pixels:
raise ValidationError(
self.error_messages["file_too_many_pixels"] % (num_pixels),
self.error_messages["file_too_many_pixels"]
% {"num_pixels": num_pixels, "max_pixels_count": self.max_image_pixels},
code="file_too_many_pixels",
)

Wyświetl plik

@ -203,7 +203,7 @@ def edit(request, image_id):
messages.success(
request,
_("Image '{0}' updated.").format(image.title),
_("Image '%(image_title)s' updated.") % {"image_title": image.title},
buttons=[messages.button(edit_url, _("Edit again"))],
)
return redirect(redirect_url)
@ -226,7 +226,8 @@ def edit(request, image_id):
request,
_(
"The source image file could not be found. Please change the source or delete the image."
).format(image.title),
)
% {"image_title": image.title},
buttons=[
messages.button(
reverse("wagtailimages:delete", args=(image.id,)), _("Delete")
@ -349,7 +350,10 @@ def delete(request, image_id):
if request.method == "POST":
image.delete()
messages.success(request, _("Image '{0}' deleted.").format(image.title))
messages.success(
request,
_("Image '%(image_title)s' deleted.") % {"image_title": image.title},
)
return redirect(next_url) if next_url else redirect("wagtailimages:index")
return TemplateResponse(
@ -375,7 +379,7 @@ def add(request):
messages.success(
request,
_("Image '{0}' added.").format(image.title),
_("Image '%(image_title)s' added.") % {"image_title": image.title},
buttons=[
messages.button(
reverse("wagtailimages:edit", args=(image.id,)), _("Edit")
@ -415,11 +419,13 @@ def usage(request, image_id):
for object, references in object_page:
edit_url = url_finder.get_edit_url(object)
if edit_url is None:
label = _("(Private %s)") % object._meta.verbose_name
label = _("(Private %(object)s)") % {"object": object._meta.verbose_name}
edit_link_title = None
else:
label = str(object)
edit_link_title = _("Edit this %s") % object._meta.verbose_name
edit_link_title = _("Edit this %(object)s") % {
"object": object._meta.verbose_name
}
results.append((label, edit_url, edit_link_title, references))
return TemplateResponse(

Wyświetl plik

@ -48,12 +48,12 @@ class IndexView(generic.IndexView):
class CreateView(generic.CreateView):
page_title = gettext_lazy("Add locale")
success_message = gettext_lazy("Locale '{0}' created.")
success_message = gettext_lazy("Locale '%(object)s' created.")
template_name = "wagtaillocales/create.html"
class EditView(generic.EditView):
success_message = gettext_lazy("Locale '{0}' updated.")
success_message = gettext_lazy("Locale '%(object)s' updated.")
error_message = gettext_lazy("The locale could not be saved due to errors.")
delete_item_label = gettext_lazy("Delete locale")
context_object_name = "locale"
@ -62,7 +62,7 @@ class EditView(generic.EditView):
class DeleteView(generic.DeleteView):
success_message = gettext_lazy("Locale '{0}' deleted.")
success_message = gettext_lazy("Locale '%(object)s' deleted.")
page_title = gettext_lazy("Delete locale")
confirmation_message = gettext_lazy("Are you sure you want to delete this locale?")
template_name = "wagtaillocales/confirm_delete.html"

Wyświetl plik

@ -45,29 +45,37 @@ class BasicLock(BaseLock):
if self.page.locked_by_id == user.pk:
if self.page.locked_at:
return format_html(
_("<b>Page '{}' was locked</b> by <b>you</b> on <b>{}</b>."),
self.page.get_admin_display_title(),
self.page.locked_at.strftime("%d %b %Y %H:%M"),
# nosemgrep: translation-no-new-style-formatting (new-style only w/ format_html)
_(
"<b>Page '{page_title}' was locked</b> by <b>you</b> on <b>{datetime}</b>."
),
page_title=self.page.get_admin_display_title(),
datetime=self.page.locked_at.strftime("%d %b %Y %H:%M"),
)
else:
return format_html(
_("<b>Page '{}' is locked</b> by <b>you</b>."),
self.page.get_admin_display_title(),
# nosemgrep: translation-no-new-style-formatting (new-style only w/ format_html)
_("<b>Page '{page_title}' is locked</b> by <b>you</b>."),
page_title=self.page.get_admin_display_title(),
)
else:
if self.page.locked_by and self.page.locked_at:
return format_html(
_("<b>Page '{}' was locked</b> by <b>{}</b> on <b>{}</b>."),
self.page.get_admin_display_title(),
str(self.page.locked_by),
self.page.locked_at.strftime("%d %b %Y %H:%M"),
# nosemgrep: translation-no-new-style-formatting (new-style only w/ format_html)
_(
"<b>Page '{page_title}' was locked</b> by <b>{user}</b> on <b>{datetime}</b>."
),
page_title=self.page.get_admin_display_title(),
user=str(self.page.locked_by),
datetime=self.page.locked_at.strftime("%d %b %Y %H:%M"),
)
else:
# Page was probably locked with an old version of Wagtail, or a script
return format_html(
_("<b>Page '{}' is locked</b>."),
self.page.get_admin_display_title(),
# nosemgrep: translation-no-new-style-formatting (new-style only w/ format_html)
_("<b>Page '{page_title}' is locked</b>."),
page_title=self.page.get_admin_display_title(),
)
@ -92,9 +100,12 @@ class WorkflowLock(BaseLock):
workflow_info = _("This page is currently awaiting moderation.")
else:
workflow_info = format_html(
_("This page is awaiting <b>'{}'</b> in the <b>'{}'</b> workflow."),
self.task.name,
self.page.current_workflow_state.workflow.name,
# nosemgrep: translation-no-new-style-formatting (new-style only w/ format_html)
_(
"This page is awaiting <b>'{task_name}'</b> in the <b>'{workflow_name}'</b> workflow."
),
task_name=self.task.name,
workflow_name=self.page.current_workflow_state.workflow.name,
)
return mark_safe(
@ -122,7 +133,10 @@ class ScheduledForPublishLock(BaseLock):
scheduled_revision = self.page.scheduled_revision
return format_html(
_("Page '{}' is locked and has been scheduled to go live at {}"),
self.page.get_admin_display_title(),
scheduled_revision.approved_go_live_at.strftime("%d %b %Y %H:%M"),
# nosemgrep: translation-no-new-style-formatting (new-style only w/ format_html)
_(
"Page '{page_title}' is locked and has been scheduled to go live at {datetime}"
),
page_title=self.page.get_admin_display_title(),
datetime=scheduled_revision.approved_go_live_at.strftime("%d %b %Y %H:%M"),
)

Wyświetl plik

@ -3871,9 +3871,13 @@ class WorkflowState(models.Model):
return super().save(*args, **kwargs)
def __str__(self):
return _("Workflow '{0}' on Page '{1}': {2}").format(
self.workflow, self.page, self.status
)
return _(
"Workflow '%(workflow_name)s' on Page '%(page_title)s': %(status)s"
) % {
"workflow_name": self.workflow,
"page_title": self.page,
"status": self.status,
}
def resume(self, user=None):
"""Put a STATUS_NEEDS_CHANGES workflow state back into STATUS_IN_PROGRESS, and restart the current task"""
@ -4231,9 +4235,13 @@ class TaskState(models.Model):
self.content_type = ContentType.objects.get_for_model(self)
def __str__(self):
return _("Task '{0}' on Page Revision '{1}': {2}").format(
self.task, self.page_revision, self.status
)
return _(
"Task '%(task_name)s' on Page Revision '%(revision_info)s': %(status)s"
) % {
"task_name": self.task,
"revision_info": self.page_revision,
"status": self.status,
}
@cached_property
def specific(self):

Wyświetl plik

@ -201,9 +201,10 @@ class BaseLogEntry(models.Model):
if not log_action_registry.action_exists(self.action):
raise ValidationError(
{
"action": _("The log action '{}' has not been registered.").format(
self.action
"action": _(
"The log action '%(action_name)s' has not been registered."
)
% {"action_name": self.action}
}
)

Wyświetl plik

@ -31,12 +31,12 @@ class IndexView(generic.IndexView):
class CreateView(generic.CreateView):
page_title = _("Add site")
success_message = _("Site '{0}' created.")
success_message = _("Site '%(object)s' created.")
template_name = "wagtailsites/create.html"
class EditView(generic.EditView):
success_message = _("Site '{0}' updated.")
success_message = _("Site '%(object)s' updated.")
error_message = _("The site could not be saved due to errors.")
delete_item_label = _("Delete site")
context_object_name = "site"
@ -44,7 +44,7 @@ class EditView(generic.EditView):
class DeleteView(generic.DeleteView):
success_message = _("Site '{0}' deleted.")
success_message = _("Site '%(object)s' deleted.")
page_title = _("Delete site")
confirmation_message = _("Are you sure you want to delete this site?")

Wyświetl plik

@ -31,16 +31,16 @@ class DeleteBulkAction(SnippetBulkAction):
def get_success_message(self, num_parent_objects, num_child_objects):
if num_parent_objects == 1:
return _("%(snippet_type)s '%(instance)s' deleted.") % {
"snippet_type": capfirst(self.model._meta.verbose_name),
"instance": self.actionable_objects[0],
return _("%(model_name)s '%(object)s' deleted.") % {
"model_name": capfirst(self.model._meta.verbose_name),
"object": self.actionable_objects[0],
}
else:
return ngettext(
"%(count)d %(snippet_type)s deleted.",
"%(count)d %(snippet_type)s deleted.",
"%(count)d %(model_name)s deleted.",
"%(count)d %(model_name)s deleted.",
num_parent_objects,
) % {
"snippet_type": capfirst(self.model._meta.verbose_name_plural),
"model_name": capfirst(self.model._meta.verbose_name_plural),
"count": num_parent_objects,
}

Wyświetl plik

@ -243,17 +243,17 @@ class CreateView(generic.CreateView):
return reverse(self.index_url_name) + urlquery
def get_success_message(self, instance):
message = _("%(snippet_type)s '%(instance)s' created.")
message = _("%(model_name)s '%(object)s' created.")
if isinstance(instance, DraftStateMixin) and self.action == "publish":
message = _("%(snippet_type)s '%(instance)s' created and published.")
message = _("%(model_name)s '%(object)s' created and published.")
if instance.go_live_at and instance.go_live_at > timezone.now():
message = _(
"%(snippet_type)s '%(instance)s' created and scheduled for publishing."
"%(model_name)s '%(object)s' created and scheduled for publishing."
)
return message % {
"snippet_type": capfirst(self.model._meta.verbose_name),
"instance": instance,
"model_name": capfirst(self.model._meta.verbose_name),
"object": instance,
}
def get_success_buttons(self):
@ -376,24 +376,24 @@ class EditView(generic.EditView):
return reverse(self.index_url_name)
def get_success_message(self):
message = _("%(snippet_type)s '%(instance)s' updated.")
message = _("%(model_name)s '%(object)s' updated.")
if self.draftstate_enabled and self.action == "publish":
message = _("%(snippet_type)s '%(instance)s' updated and published.")
message = _("%(model_name)s '%(object)s' updated and published.")
if self.object.go_live_at and self.object.go_live_at > timezone.now():
message = _(
"%(snippet_type)s '%(instance)s' has been scheduled for publishing."
"%(model_name)s '%(object)s' has been scheduled for publishing."
)
if self.object.live:
message = _(
"%(snippet_type)s '%(instance)s' is live and this version has been scheduled for publishing."
"%(model_name)s '%(object)s' is live and this version has been scheduled for publishing."
)
return message % {
"snippet_type": capfirst(self.model._meta.verbose_name),
"instance": self.object,
"model_name": capfirst(self.model._meta.verbose_name),
"object": self.object,
}
def get_success_buttons(self):
@ -506,20 +506,20 @@ class DeleteView(generic.DeleteView):
def get_success_message(self):
count = len(self.objects)
if count == 1:
return _("%(snippet_type)s '%(instance)s' deleted.") % {
"snippet_type": capfirst(self.model._meta.verbose_name),
"instance": self.objects[0],
return _("%(model_name)s '%(object)s' deleted.") % {
"model_name": capfirst(self.model._meta.verbose_name),
"object": self.objects[0],
}
# This message is only used in plural form, but we'll define it with ngettext so that
# languages with multiple plural forms can be handled correctly (or, at least, as
# correctly as possible within the limitations of verbose_name_plural...)
return ngettext(
"%(count)d %(snippet_type)s deleted.",
"%(count)d %(snippet_type)s deleted.",
"%(count)d %(model_name)s deleted.",
"%(count)d %(model_name)s deleted.",
count,
) % {
"snippet_type": capfirst(self.model._meta.verbose_name_plural),
"model_name": capfirst(self.model._meta.verbose_name_plural),
"count": count,
}
@ -590,11 +590,15 @@ class UsageView(generic.IndexView):
for object, references in context.get("page_obj"):
edit_url = url_finder.get_edit_url(object)
if edit_url is None:
label = _("(Private %s)") % object._meta.verbose_name
label = _("(Private %(object)s)") % {
"object": object._meta.verbose_name
}
edit_link_title = None
else:
label = str(object)
edit_link_title = _("Edit this %s") % object._meta.verbose_name
edit_link_title = _("Edit this %(object)s") % {
"object": object._meta.verbose_name
}
results.append((label, edit_url, edit_link_title, references))
context.update(
@ -729,15 +733,15 @@ class RevisionsCompareView(PermissionCheckedMixin, generic.RevisionsCompareView)
@property
def edit_label(self):
return _("Edit this {model_name}").format(
model_name=self.model._meta.verbose_name
)
return _("Edit this %(model_name)s") % {
"model_name": self.model._meta.verbose_name
}
@property
def history_label(self):
return _("{model_name} history").format(
model_name=self.model._meta.verbose_name
)
return _("%(model_name)s history") % {
"model_name": self.model._meta.verbose_name
}
class UnpublishView(PermissionCheckedMixin, generic.UnpublishView):

Wyświetl plik

@ -21,9 +21,9 @@ class AdminSnippetChooser(BaseChooser):
def __init__(self, model, **kwargs):
self.model = model
name = self.model._meta.verbose_name
self.choose_one_text = _("Choose %s") % name
self.choose_another_text = _("Choose another %s") % name
self.link_to_chosen_text = _("Edit this %s") % name
self.choose_one_text = _("Choose %(object)s") % {"object": name}
self.choose_another_text = _("Choose another %(object)s") % {"object": name}
self.link_to_chosen_text = _("Edit this %(object)s") % {"object": name}
super().__init__(**kwargs)

Wyświetl plik

@ -80,7 +80,7 @@ class IndexView(generic.IndexView):
class CreateView(PermissionPanelFormsMixin, generic.CreateView):
page_title = _("Add group")
success_message = _("Group '{0}' created.")
success_message = _("Group '%(object)s' created.")
template_name = "wagtailusers/groups/create.html"
def post(self, request, *args, **kwargs):
@ -116,7 +116,7 @@ class CreateView(PermissionPanelFormsMixin, generic.CreateView):
class EditView(PermissionPanelFormsMixin, generic.EditView):
success_message = _("Group '{0}' updated.")
success_message = _("Group '%(object)s' updated.")
error_message = _("The group could not be saved due to errors.")
delete_item_label = _("Delete group")
context_object_name = "group"
@ -154,7 +154,7 @@ class EditView(PermissionPanelFormsMixin, generic.EditView):
class DeleteView(generic.DeleteView):
success_message = _("Group '{0}' deleted.")
success_message = _("Group '%(object)s' deleted.")
page_title = _("Delete group")
confirmation_message = _("Are you sure you want to delete this group?")
template_name = "wagtailusers/groups/confirm_delete.html"

Wyświetl plik

@ -178,12 +178,12 @@ class Edit(EditView):
index_url_name = "wagtailusers_users:index"
edit_url_name = "wagtailusers_users:edit"
delete_url_name = "wagtailusers_users:delete"
success_message = _("User '{0}' updated.")
success_message = _("User '%(object)s' updated.")
context_object_name = "user"
error_message = gettext_lazy("The user could not be saved due to errors.")
def get_page_title(self):
return _("Editing %s") % self.object.get_username()
return _("Editing %(object)s") % {"object": self.object.get_username()}
def get_page_subtitle(self):
return ""
@ -250,7 +250,7 @@ class Delete(DeleteView):
index_url_name = "wagtailusers_users:index"
page_title = gettext_lazy("Delete user")
context_object_name = "user"
success_message = _("User '{0}' deleted.")
success_message = _("User '%(object)s' deleted.")
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()