Allow translators to control word order of BatchAction message

Currently BatchAction message is generated by concatenating
'action' and 'data_type'. In most cases 'action' is a verb
and 'data_type' is an object. However in some action such as
TerminateInstance 'action' is a combination of verb and
object (e.g., "Scheduled termination of").  It is hard that
translators find translated strings which covers both cases.

This commit allows classes inheriting BatchAction to specify
a full action name containing '%(data_type)s' substitution
in action_present/ action_past so that translators can
control the order of phrases completely.

* _conjugate() in actions.py is renamed to _get_action_name()
  because this method no longer always conjugate words.
* Changes action_past strings which are not a form of verb + obj
  to strings with "%(data_type)s"

Closes-Bug: #1253678
Change-Id: I3cf1dc74e22745eb3a9bd2edc3ec68ed389495b5
This commit is contained in:
Akihiro MOTOKI 2013-11-21 07:01:10 +09:00 committed by Akihiro Motoki
parent 5cd6efb668
commit 0ef5c4701f
10 changed files with 46 additions and 32 deletions

View File

@ -528,6 +528,13 @@ class BatchAction(Action):
self.current_present_action = n will set the current active item
from the list(action_present[n])
You can pass a complete action name including 'data_type' by specifying
'%(data_type)s' substitution in action_present ("Delete %(data_type)s").
Otherwise a complete action name is a format of "<action> <data_type>".
<data_type> is determined based on the number of items.
By passing a complete action name you allow translators to control
the order of words as they want.
.. attribute:: action_past
String or tuple/list. The past tense of action_present. ("Deleted",
@ -559,9 +566,9 @@ class BatchAction(Action):
self.data_type_plural = kwargs.get('data_type_plural',
self.data_type_singular + 's')
# If setting a default name, don't initialize it too early
self.verbose_name = kwargs.get('verbose_name', self._conjugate)
self.verbose_name = kwargs.get('verbose_name', self._get_action_name)
self.verbose_name_plural = kwargs.get('verbose_name_plural',
lambda: self._conjugate('plural'))
lambda: self._get_action_name('plural'))
if not kwargs.get('data_type_singular', None):
raise NotImplementedError('A batchAction object must have a '
@ -579,7 +586,7 @@ class BatchAction(Action):
return False
return super(BatchAction, self)._allowed(request, datum)
def _conjugate(self, items=None, past=False):
def _get_action_name(self, items=None, past=False):
"""Builds combinations like 'Delete Object' and 'Deleted
Objects' based on the number of items and `past` flag.
"""
@ -594,10 +601,14 @@ class BatchAction(Action):
data_type = self.data_type_singular
else:
data_type = self.data_type_plural
if action_type == "past":
msgstr = pgettext_lazy("past", "%(action)s %(data_type)s")
if '%(data_type)s' in action:
# If full action string is specified, use action as format string.
msgstr = action
else:
msgstr = pgettext_lazy("present", "%(action)s %(data_type)s")
if action_type == "past":
msgstr = pgettext_lazy("past", "%(action)s %(data_type)s")
else:
msgstr = pgettext_lazy("present", "%(action)s %(data_type)s")
return msgstr % {'action': action, 'data_type': data_type}
def action(self, request, datum_id):
@ -606,14 +617,14 @@ class BatchAction(Action):
Return values are discarded, errors raised are caught and logged.
"""
raise NotImplementedError('action() must be defined for '
'BatchAction: %s' % self.data_type_singular)
raise NotImplementedError('action() must be defined for %s'
% self.__class__.__name__)
def update(self, request, datum):
"""Switches the action verbose name, if needed."""
if getattr(self, 'action_present', False):
self.verbose_name = self._conjugate()
self.verbose_name_plural = self._conjugate('plural')
self.verbose_name = self._get_action_name()
self.verbose_name_plural = self._get_action_name('plural')
def get_success_url(self, request=None):
"""Returns the URL to redirect to after a successful action."""
@ -631,7 +642,8 @@ class BatchAction(Action):
if not table._filter_action(self, request, datum):
action_not_allowed.append(datum_display)
LOG.info('Permission denied to %s: "%s"' %
(self._conjugate(past=True).lower(), datum_display))
(self._get_action_name(past=True).lower(),
datum_display))
continue
try:
self.action(request, datum_id)
@ -640,7 +652,7 @@ class BatchAction(Action):
action_success.append(datum_display)
self.success_ids.append(datum_id)
LOG.info('%s: "%s"' %
(self._conjugate(past=True), datum_display))
(self._get_action_name(past=True), datum_display))
except Exception as ex:
# Handle the exception but silence it since we'll display
# an aggregate error message later. Otherwise we'd get
@ -656,19 +668,21 @@ class BatchAction(Action):
success_message_level = messages.success
if action_not_allowed:
msg = _('You are not allowed to %(action)s: %(objs)s')
params = {"action": self._conjugate(action_not_allowed).lower(),
params = {"action":
self._get_action_name(action_not_allowed).lower(),
"objs": functions.lazy_join(", ", action_not_allowed)}
messages.error(request, msg % params)
success_message_level = messages.info
if action_failure:
msg = _('Unable to %(action)s: %(objs)s')
params = {"action": self._conjugate(action_failure).lower(),
params = {"action": self._get_action_name(action_failure).lower(),
"objs": functions.lazy_join(", ", action_failure)}
messages.error(request, msg % params)
success_message_level = messages.info
if action_success:
msg = _('%(action)s: %(objs)s')
params = {"action": self._conjugate(action_success, True),
params = {"action":
self._get_action_name(action_success, past=True),
"objs": functions.lazy_join(", ", action_success)}
success_message_level(request, msg % params)

View File

@ -55,7 +55,7 @@ class RestoreLink(tables.LinkAction):
class DeleteBackup(tables.BatchAction):
name = "delete"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Backup")
data_type_plural = _("Backups")
classes = ('btn-danger', 'btn-terminate')

View File

@ -34,7 +34,7 @@ ACTIVE_STATES = ("ACTIVE",)
class TerminateInstance(tables.BatchAction):
name = "terminate"
action_present = _("Terminate")
action_past = _("Scheduled termination of")
action_past = _("Scheduled termination of %(data_type)s")
data_type_singular = _("Instance")
data_type_plural = _("Instances")
classes = ('btn-danger', 'btn-terminate')

View File

@ -46,7 +46,7 @@ class AddFirewallLink(tables.LinkAction):
class DeleteRuleLink(tables.DeleteAction):
name = "deleterule"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Rule")
data_type_plural = _("Rules")
@ -54,7 +54,7 @@ class DeleteRuleLink(tables.DeleteAction):
class DeletePolicyLink(tables.DeleteAction):
name = "deletepolicy"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Policy")
data_type_plural = _("Policies")
@ -62,7 +62,7 @@ class DeletePolicyLink(tables.DeleteAction):
class DeleteFirewallLink(tables.DeleteAction):
name = "deletefirewall"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Firewall")
data_type_plural = _("Firewalls")

View File

@ -32,7 +32,7 @@ from openstack_dashboard.dashboards.project.volumes \
class DeleteVolumeSnapshot(tables.DeleteAction):
data_type_singular = _("Volume Snapshot")
data_type_plural = _("Volume Snapshots")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
def delete(self, request, obj_id):
api.cinder.volume_snapshot_delete(request, obj_id)

View File

@ -73,7 +73,7 @@ def is_deleting(instance):
class TerminateInstance(tables.BatchAction):
name = "terminate"
action_present = _("Terminate")
action_past = _("Scheduled termination of")
action_past = _("Scheduled termination of %(data_type)s")
data_type_singular = _("Instance")
data_type_plural = _("Instances")
classes = ('btn-danger', 'btn-terminate')

View File

@ -66,7 +66,7 @@ class AddMonitorLink(tables.LinkAction):
class DeleteVipLink(tables.DeleteAction):
name = "deletevip"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("VIP")
data_type_plural = _("VIPs")
@ -79,7 +79,7 @@ class DeleteVipLink(tables.DeleteAction):
class DeletePoolLink(tables.DeleteAction):
name = "deletepool"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Pool")
data_type_plural = _("Pools")
@ -92,7 +92,7 @@ class DeletePoolLink(tables.DeleteAction):
class DeleteMonitorLink(tables.DeleteAction):
name = "deletemonitor"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Monitor")
data_type_plural = _("Monitors")
@ -100,7 +100,7 @@ class DeleteMonitorLink(tables.DeleteAction):
class DeleteMemberLink(tables.DeleteAction):
name = "deletemember"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Member")
data_type_plural = _("Members")

View File

@ -37,7 +37,7 @@ class LaunchStack(tables.LinkAction):
class DeleteStack(tables.BatchAction):
name = "delete"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("Stack")
data_type_plural = _("Stacks")
classes = ('btn-danger', 'btn-terminate')

View File

@ -37,7 +37,7 @@ DELETABLE_STATES = ("available", "error")
class DeleteVolume(tables.DeleteAction):
data_type_singular = _("Volume")
data_type_plural = _("Volumes")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
def delete(self, request, obj_id):
obj = self.table.get_object_by_id(obj_id)

View File

@ -55,7 +55,7 @@ class AddIPSecSiteConnectionLink(tables.LinkAction):
class DeleteVPNServiceLink(tables.DeleteAction):
name = "deletevpnservice"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("VPN Service")
data_type_plural = _("VPN Services")
@ -63,7 +63,7 @@ class DeleteVPNServiceLink(tables.DeleteAction):
class DeleteIKEPolicyLink(tables.DeleteAction):
name = "deleteikepolicy"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("IKE Policy")
data_type_plural = _("IKE Policies")
@ -71,7 +71,7 @@ class DeleteIKEPolicyLink(tables.DeleteAction):
class DeleteIPSecPolicyLink(tables.DeleteAction):
name = "deleteipsecpolicy"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("IPSec Policy")
data_type_plural = _("IPSec Policies")
@ -79,7 +79,7 @@ class DeleteIPSecPolicyLink(tables.DeleteAction):
class DeleteIPSecSiteConnectionLink(tables.DeleteAction):
name = "deleteipsecsiteconnection"
action_present = _("Delete")
action_past = _("Scheduled deletion of")
action_past = _("Scheduled deletion of %(data_type)s")
data_type_singular = _("IPSec Site Connection")
data_type_plural = _("IPSec Site Connections")