usage: Use tenant_quota_usages() for Limit Summary

Previously we have almost duplicated logic for the limit summary
and quota usages. This commit shares the logic.

blueprint make-quotas-great-again
Change-Id: Ie5d7e51c9424701fdbcbfbcb1032c0b833a69371
This commit is contained in:
Akihiro Motoki 2017-12-09 18:01:27 +09:00
parent 0e0db28969
commit 7fabda7ef1
6 changed files with 117 additions and 370 deletions

View File

@ -12,11 +12,11 @@
<div class="quota_title" title="{{ quota.name }}" data-toggle="tooltip"> {{ quota.name }}</div>
<div class="quota_subtitle">
{% if quota.max|quotainf != '-1' %}
{% if quota.type == "totalRAMUsed" %}
{% if quota.type == "ram" %}
{% blocktrans trimmed with usedphrase=quota.text used=quota.used|mb_float_format available=quota.max|quotainf|mb_float_format %}
{{ usedphrase }} <span> {{ used }} </span>of<span> {{ available }} </span>
{% endblocktrans %}
{% elif quota.type == "totalGigabytesUsed" %}
{% elif quota.type == "gigabytes" %}
{% blocktrans trimmed with usedphrase=quota.text used=quota.used|diskgbformat available=quota.max|quotainf|diskgbformat %}
{{ usedphrase }} <span> {{ used }} </span>of<span> {{ available }} </span>
{% endblocktrans %}

View File

@ -37,77 +37,65 @@ class UsageViewTests(test.TestCase):
@test.create_mocks({api.nova: (
'usage_get',
('tenant_absolute_limits', 'nova_tenant_absolute_limits'),
'extension_supported',
)})
def _stub_nova_api_calls(self,
nova_stu_enabled=True,
tenant_limits_exception=False,
stu_exception=False, overview_days_range=1):
def _stub_api_calls(self, nova_stu_enabled=True,
stu_exception=False, overview_days_range=1,
quota_usage_overrides=None):
self.mock_extension_supported.side_effect = [nova_stu_enabled,
nova_stu_enabled]
if tenant_limits_exception:
self.mock_nova_tenant_absolute_limits.side_effect = \
tenant_limits_exception
else:
self.mock_nova_tenant_absolute_limits.return_value = \
self.limits['absolute']
if nova_stu_enabled:
self._nova_stu_enabled(stu_exception,
overview_days_range=overview_days_range)
def _check_nova_api_calls(self,
nova_stu_enabled=True,
tenant_limits_exception=False,
stu_exception=False, overview_days_range=1):
self._stub_tenant_quota_usages(overrides=quota_usage_overrides)
def _check_api_calls(self, nova_stu_enabled=True,
stu_exception=False, overview_days_range=1):
self.assert_mock_multiple_calls_with_same_arguments(
self.mock_extension_supported, 2,
mock.call('SimpleTenantUsage', test.IsHttpRequest()))
self.mock_nova_tenant_absolute_limits.assert_called_once_with(
test.IsHttpRequest(), reserved=True)
if nova_stu_enabled:
self._check_stu_enabled(stu_exception,
overview_days_range=overview_days_range)
else:
self.mock_usage_get.assert_not_called()
self._check_tenant_quota_usages()
@test.create_mocks({api.cinder: (
('tenant_absolute_limits', 'cinder_tenant_absolute_limits'),
)})
def _stub_cinder_api_calls(self):
self.mock_cinder_tenant_absolute_limits.return_value = \
self.cinder_limits['absolute']
@staticmethod
def _add_quota_usages(usages, quota_usages, excludes=None):
excludes = excludes or []
for k in quota_usages.usages:
if k in excludes:
continue
quota = quota_usages[k]['quota']
if quota == float('inf'):
quota = -1
usages.add_quota(api.base.Quota(k, quota))
usages.tally(k, quota_usages[k]['used'])
def _check_cinder_api_calls(self):
self.mock_cinder_tenant_absolute_limits.assert_called_once_with(
@test.create_mocks({usage.quotas: ('tenant_quota_usages',)})
def _stub_tenant_quota_usages(self, overrides):
usages_data = usage.quotas.QuotaUsage()
self._add_quota_usages(usages_data, self.quota_usages.first(),
# At now, nova quota_usages contains
# volumes and gigabytes.
excludes=('volumes', 'gigabytes'))
self._add_quota_usages(
usages_data, self.neutron_quota_usages.first())
self._add_quota_usages(usages_data, self.cinder_quota_usages.first())
if overrides:
for key, value in overrides.items():
if 'quota' in value:
usages_data.add_quota(api.base.Quota(key, value['quota']))
if 'used' in value:
usages_data.tally(key, value['used'])
self.mock_tenant_quota_usages.return_value = usages_data
def _check_tenant_quota_usages(self):
self.mock_tenant_quota_usages.assert_called_once_with(
test.IsHttpRequest())
@test.create_mocks({api.neutron: ('security_group_list',
'tenant_floating_ip_list',
'floating_ip_supported',
'is_extension_supported')})
def _stub_neutron_api_calls(self, neutron_sg_enabled=True):
self.mock_is_extension_supported.return_value = neutron_sg_enabled
self.mock_floating_ip_supported.return_value = True
self.mock_tenant_floating_ip_list.return_value = \
self.floating_ips.list()
if neutron_sg_enabled:
self.mock_security_group_list.return_value = \
self.security_groups.list()
def _check_neutron_api_calls(self, neutron_sg_enabled=True):
self.mock_is_extension_supported.assert_called_once_with(
test.IsHttpRequest(), 'security-group')
self.mock_floating_ip_supported.assert_called_once_with(
test.IsHttpRequest())
self.mock_tenant_floating_ip_list.assert_called_once_with(
test.IsHttpRequest())
if neutron_sg_enabled:
self.mock_security_group_list.assert_called_once_with(
test.IsHttpRequest())
else:
self.mock_security_group_list.assert_not_called()
def _nova_stu_enabled(self, exception=False, overview_days_range=1):
if exception:
self.mock_usage_get.side_effect = exception
@ -129,7 +117,7 @@ class UsageViewTests(test.TestCase):
test.IsHttpRequest(), self.tenant.id, start, end)
def _common_assertions(self, nova_stu_enabled,
maxTotalFloatingIps=float("inf")):
maxTotalFloatingIps=50):
res = self.client.get(reverse('horizon:project:overview:index'))
usages = res.context['usage']
self.assertTemplateUsed(res, 'project/overview/usage.html')
@ -140,7 +128,7 @@ class UsageViewTests(test.TestCase):
self.assertContains(res, 'form-inline')
else:
self.assertNotContains(res, 'form-inline')
self.assertEqual(usages.limits['maxTotalFloatingIps'],
self.assertEqual(usages.limits['floatingip']['quota'],
maxTotalFloatingIps)
@override_settings(OVERVIEW_DAYS_RANGE=None)
@ -155,37 +143,13 @@ class UsageViewTests(test.TestCase):
self._test_usage(nova_stu_enabled=False, overview_days_range=None)
def _test_usage(self, nova_stu_enabled, overview_days_range=1):
self._stub_nova_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
self._stub_neutron_api_calls()
self._stub_cinder_api_calls()
self._stub_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
self._common_assertions(nova_stu_enabled)
self._check_nova_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
self._check_neutron_api_calls()
self._check_cinder_api_calls()
def test_usage_nova_network(self):
self._test_usage_nova_network(nova_stu_enabled=True)
def test_usage_nova_network_disabled(self):
self._test_usage_nova_network(nova_stu_enabled=False)
@test.create_mocks({api.base: ('is_service_enabled',),
api.cinder: ('is_volume_service_enabled',)})
def _test_usage_nova_network(self, nova_stu_enabled):
self._stub_nova_api_calls(nova_stu_enabled)
self.mock_is_service_enabled.return_value = False
self.mock_is_volume_service_enabled.return_value = False
self._common_assertions(nova_stu_enabled, maxTotalFloatingIps=10)
self._check_nova_api_calls(nova_stu_enabled)
self.mock_is_service_enabled.assert_called_once_with(
test.IsHttpRequest(), 'network')
self.mock_is_volume_service_enabled.assert_called_once_with(
test.IsHttpRequest())
self._check_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
def test_unauthorized(self):
url = reverse('horizon:admin:volumes:index')
@ -209,59 +173,33 @@ class UsageViewTests(test.TestCase):
self._test_usage_csv(nova_stu_enabled=False)
def _test_usage_csv(self, nova_stu_enabled=True, overview_days_range=1):
self._stub_nova_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
self._stub_neutron_api_calls()
self._stub_cinder_api_calls()
self._stub_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
res = self.client.get(reverse('horizon:project:overview:index') +
"?format=csv")
self.assertTemplateUsed(res, 'project/overview/usage.csv')
self.assertIsInstance(res.context['usage'], usage.ProjectUsage)
self._check_nova_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
self._check_neutron_api_calls()
self._check_cinder_api_calls()
self._check_api_calls(nova_stu_enabled,
overview_days_range=overview_days_range)
def test_usage_exception_usage(self):
self._stub_nova_api_calls(stu_exception=self.exceptions.nova)
self._stub_neutron_api_calls()
self._stub_cinder_api_calls()
self._stub_api_calls(stu_exception=self.exceptions.nova)
res = self.client.get(reverse('horizon:project:overview:index'))
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertEqual(res.context['usage'].usage_list, [])
self._check_nova_api_calls(stu_exception=self.exceptions.nova)
self._check_neutron_api_calls()
self._check_cinder_api_calls()
def test_usage_exception_quota(self):
self._stub_nova_api_calls(tenant_limits_exception=self.exceptions.nova)
self._stub_neutron_api_calls()
self._stub_cinder_api_calls()
res = self.client.get(reverse('horizon:project:overview:index'))
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertEqual(res.context['usage'].quotas, {})
self._check_nova_api_calls(
tenant_limits_exception=self.exceptions.nova)
self._check_neutron_api_calls()
self._check_cinder_api_calls()
self._check_api_calls(stu_exception=self.exceptions.nova)
def test_usage_default_tenant(self):
self._stub_nova_api_calls()
self._stub_neutron_api_calls()
self._stub_cinder_api_calls()
self._stub_api_calls()
res = self.client.get(reverse('horizon:project:overview:index'))
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertIsInstance(res.context['usage'], usage.ProjectUsage)
self._check_nova_api_calls()
self._check_neutron_api_calls()
self._check_cinder_api_calls()
self._check_api_calls()
@test.update_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True})
def test_usage_with_neutron(self):
@ -275,59 +213,13 @@ class UsageViewTests(test.TestCase):
def test_usage_with_neutron_floating_ip_disabled(self):
self._test_usage_with_neutron(neutron_fip_enabled=False)
def _test_usage_with_neutron_prepare(self):
self._stub_nova_api_calls()
self._stub_cinder_api_calls()
def _check_nova_cinder_calls_with_neutron_prepare(self):
self._check_nova_api_calls()
self._check_cinder_api_calls()
@test.create_mocks({api.neutron: ('tenant_quota_get',
'is_extension_supported',
'floating_ip_supported',
'tenant_floating_ip_list',
'security_group_list')})
def _test_usage_with_neutron(self,
neutron_sg_enabled=True,
neutron_fip_enabled=True):
self._test_usage_with_neutron_prepare()
self.mock_is_extension_supported.side_effect = [True,
neutron_sg_enabled]
self.mock_floating_ip_supported.return_value = neutron_fip_enabled
if neutron_fip_enabled:
self.mock_tenant_floating_ip_list.return_value = \
self.floating_ips.list()
if neutron_sg_enabled:
self.mock_security_group_list.return_value = \
self.security_groups.list()
self.mock_tenant_quota_get.return_value = self.neutron_quotas.first()
self._stub_api_calls()
self._test_usage_with_neutron_check(neutron_sg_enabled,
neutron_fip_enabled)
self.mock_is_extension_supported.assert_has_calls([
mock.call(test.IsHttpRequest(), 'quotas'),
mock.call(test.IsHttpRequest(), 'security-group'),
])
self.assertEqual(2, self.mock_is_extension_supported.call_count)
self.mock_floating_ip_supported.assert_called_once_with(
test.IsHttpRequest())
if neutron_fip_enabled:
self.mock_tenant_floating_ip_list.assert_called_once_with(
test.IsHttpRequest())
else:
self.mock_tenant_floating_ip_list.assert_not_called()
if neutron_sg_enabled:
self.mock_security_group_list.assert_called_once_with(
test.IsHttpRequest())
else:
self.mock_security_group_list.assert_not_called()
self.mock_tenant_quota_get.assert_called_once_with(
test.IsHttpRequest(), self.tenant.id)
self._check_nova_cinder_calls_with_neutron_prepare()
self._check_api_calls()
def _test_usage_with_neutron_check(self, neutron_sg_enabled=True,
neutron_fip_expected=True,
@ -339,98 +231,37 @@ class UsageViewTests(test.TestCase):
self.assertContains(res, 'Security Groups')
res_limits = res.context['usage'].limits
# Make sure the floating IPs comes from Neutron (50 vs. 10)
max_floating_ips = res_limits['maxTotalFloatingIps']
max_floating_ips = res_limits['floatingip']['quota']
self.assertEqual(max_floating_ips, max_fip_expected)
if neutron_sg_enabled:
# Make sure the security group limit comes from Neutron (20 vs. 10)
max_security_groups = res_limits['maxSecurityGroups']
max_security_groups = res_limits['security_group']['quota']
self.assertEqual(max_security_groups, max_sg_expected)
@test.update_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True})
@mock.patch.object(api.neutron, 'is_extension_supported')
def test_usage_with_neutron_quotas_ext_error(self,
mock_is_extension_supported):
self._test_usage_with_neutron_prepare()
mock_is_extension_supported.side_effect = self.exceptions.neutron
self._test_usage_with_neutron_check(neutron_fip_expected=False,
max_fip_expected=float("inf"),
max_sg_expected=float("inf"))
mock_is_extension_supported.assert_called_once_with(
test.IsHttpRequest(), 'quotas')
self._check_nova_cinder_calls_with_neutron_prepare()
@test.update_settings(OPENSTACK_NEUTRON_NETWORK={'enable_quotas': True})
@mock.patch.object(api.neutron, 'is_extension_supported')
def test_usage_with_neutron_sg_ext_error(self,
mock_is_extension_supported):
self._test_usage_with_neutron_prepare()
mock_is_extension_supported.side_effect = [
True, # quotas
self.exceptions.neutron, # security-group
]
self._test_usage_with_neutron_check(neutron_fip_expected=False,
max_fip_expected=float("inf"),
max_sg_expected=float("inf"))
self.assertEqual(2, mock_is_extension_supported.call_count)
mock_is_extension_supported.assert_has_calls([
mock.call(test.IsHttpRequest(), 'quotas'),
mock.call(test.IsHttpRequest(), 'security-group'),
])
self._check_nova_cinder_calls_with_neutron_prepare()
def test_usage_with_cinder(self):
self._test_usage_cinder(cinder_enabled=True)
def test_usage_without_cinder(self):
self._test_usage_cinder(cinder_enabled=False)
@test.create_mocks({api.base: ('is_service_enabled',),
api.cinder: ('is_volume_service_enabled',)})
def _test_usage_cinder(self, cinder_enabled):
self._stub_nova_api_calls(nova_stu_enabled=True)
if cinder_enabled:
self._stub_cinder_api_calls()
self.mock_is_service_enabled.return_value = False
self.mock_is_volume_service_enabled.return_value = cinder_enabled
def test_usage_cinder(self):
self._stub_api_calls(
quota_usage_overrides={'volumes': {'used': 4},
'gigabytes': {'used': 400}}
)
res = self.client.get(reverse('horizon:project:overview:index'))
usages = res.context['usage']
self.assertTemplateUsed(res, 'project/overview/usage.html')
self.assertIsInstance(usages, usage.ProjectUsage)
if cinder_enabled:
self.assertEqual(usages.limits['totalVolumesUsed'], 4)
self.assertEqual(usages.limits['maxTotalVolumes'], 20)
self.assertEqual(usages.limits['totalGigabytesUsed'], 400)
self.assertEqual(usages.limits['maxTotalVolumeGigabytes'], 1000)
else:
self.assertNotIn('totalVolumesUsed', usages.limits)
self._check_nova_api_calls(nova_stu_enabled=True)
if cinder_enabled:
self._check_cinder_api_calls()
self.assertEqual(usages.limits['volumes']['used'], 4)
self.assertEqual(usages.limits['volumes']['quota'], 10)
self.assertEqual(usages.limits['gigabytes']['used'], 400)
self.assertEqual(usages.limits['gigabytes']['quota'], 1000)
self.mock_is_service_enabled.assert_called_once_with(
test.IsHttpRequest(), 'network')
self.mock_is_volume_service_enabled.assert_called_once_with(
test.IsHttpRequest())
self._check_api_calls(nova_stu_enabled=True)
def _test_usage_charts(self):
self._stub_nova_api_calls(nova_stu_enabled=False)
self._stub_neutron_api_calls()
self._stub_cinder_api_calls()
def _test_usage_charts(self, quota_usage_overrides=None):
self._stub_api_calls(nova_stu_enabled=False,
quota_usage_overrides=quota_usage_overrides)
res = self.client.get(reverse('horizon:project:overview:index'))
self._check_nova_api_calls(nova_stu_enabled=False)
self._check_neutron_api_calls()
self._check_cinder_api_calls()
self._check_api_calls(nova_stu_enabled=False)
return res
@ -439,9 +270,10 @@ class UsageViewTests(test.TestCase):
self.assertIn('charts', res.context)
def test_usage_charts_infinite_quota(self):
res = self._test_usage_charts()
res = self._test_usage_charts(
quota_usage_overrides={'floatingip': {'quota': -1}})
max_floating_ips = res.context['usage'].limits['maxTotalFloatingIps']
max_floating_ips = res.context['usage'].limits['floatingip']['quota']
self.assertEqual(max_floating_ips, float("inf"))
self.assertContains(res, '(No Limit)')

View File

@ -307,8 +307,8 @@ def data(TEST):
# Quota Usages
quota_usage_data = {'gigabytes': {'used': 0,
'quota': 1000},
'instances': {'used': 0,
'quota': 10},
'volumes': {'used': 0,
'quota': 10},
'snapshots': {'used': 0,
'quota': 10}}
quota_usage = usage_quotas.QuotaUsage()

View File

@ -634,7 +634,9 @@ def data(TEST):
'subnet': {'used': 0, 'quota': 5},
'port': {'used': 0, 'quota': 5},
'router': {'used': 0, 'quota': 5},
'floatingip': {'used': 0, 'quota': 10},
'floatingip': {'used': 0, 'quota': 50},
'security_group': {'used': 0, 'quota': 20},
'security_group_rule': {'used': 0, 'quota': 100},
}
quota_usage = usage_quotas.QuotaUsage()
for k, v in quota_usage_data.items():

View File

@ -23,6 +23,7 @@ from horizon import forms
from horizon import messages
from openstack_dashboard import api
from openstack_dashboard.usage import quotas
class BaseUsage(object):
@ -191,96 +192,4 @@ class ProjectUsage(BaseUsage):
return (usage,)
def get_limits(self):
try:
self.limits = api.nova.tenant_absolute_limits(self.request,
reserved=True)
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve limit information."))
self._get_neutron_limits()
self._get_cinder_limits()
def _get_neutron_usage(self, limits, resource_name):
resource_map = {
'floatingip': {
'api': api.neutron.tenant_floating_ip_list,
'limit_name': 'totalFloatingIpsUsed',
'message': _('Unable to retrieve floating IP addresses.')
},
'security_group': {
'api': api.neutron.security_group_list,
'limit_name': 'totalSecurityGroupsUsed',
'message': _('Unable to retrieve security groups.')
}
}
resource = resource_map[resource_name]
try:
method = resource['api']
current_used = len(method(self.request))
except Exception:
current_used = 0
msg = resource['message']
exceptions.handle(self.request, msg)
limits[resource['limit_name']] = current_used
def _set_neutron_limit(self, limits, neutron_quotas, resource_name):
limit_name_map = {
'floatingip': 'maxTotalFloatingIps',
'security_group': 'maxSecurityGroups',
}
if neutron_quotas is None:
resource_max = float("inf")
else:
resource_max = getattr(neutron_quotas.get(resource_name),
'limit', float("inf"))
if resource_max == -1:
resource_max = float("inf")
limits[limit_name_map[resource_name]] = resource_max
def _get_neutron_limits(self):
if not api.base.is_service_enabled(self.request, 'network'):
return
try:
neutron_quotas_supported = (
api.neutron.is_quotas_extension_supported(self.request))
neutron_sg_used = (
api.neutron.is_extension_supported(self.request,
'security-group'))
if api.neutron.floating_ip_supported(self.request):
self._get_neutron_usage(self.limits, 'floatingip')
if neutron_sg_used:
self._get_neutron_usage(self.limits, 'security_group')
# Quotas are an optional extension in Neutron. If it isn't
# enabled, assume the floating IP limit is infinite.
if neutron_quotas_supported:
neutron_quotas = api.neutron.tenant_quota_get(self.request,
self.project_id)
else:
neutron_quotas = None
except Exception:
# Assume neutron security group and quotas are enabled
# because they are enabled in most Neutron plugins.
neutron_sg_used = True
neutron_quotas = None
msg = _('Unable to retrieve network quota information.')
exceptions.handle(self.request, msg)
self._set_neutron_limit(self.limits, neutron_quotas, 'floatingip')
if neutron_sg_used:
self._set_neutron_limit(self.limits, neutron_quotas,
'security_group')
def _get_cinder_limits(self):
"""Get volume limits if cinder is enabled."""
if not api.cinder.is_volume_service_enabled(self.request):
return
try:
self.limits.update(api.cinder.tenant_absolute_limits(self.request))
except Exception:
msg = _("Unable to retrieve volume limit information.")
exceptions.handle(self.request, msg)
return
self.limits = quotas.tenant_quota_usages(self.request)

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
from django.utils.translation import pgettext_lazy
from django.utils.translation import ugettext_lazy as _
@ -83,41 +85,43 @@ class UsageView(tables.DataTableView):
return resp
ChartDef = collections.namedtuple(
'ChartDef',
('quota_key', 'label', 'used_phrase'))
# (quota key, Human Readable Name, text to display when
# describing the quota by default it is 'Used')
CHART_DEFS = [
ChartDef("instances", _("Instances"), None),
ChartDef("cores", _("VCPUs"), None),
ChartDef("ram", _("RAM"), None),
ChartDef("floatingip", _("Floating IPs"),
pgettext_lazy('Label in the limit summary', "Allocated")),
ChartDef("security_group", _("Security Groups"), None),
ChartDef("volumes", _("Volumes"), None),
ChartDef("gigabytes", _("Volume Storage"), None),
]
class ProjectUsageView(UsageView):
def _get_charts_data(self):
charts = []
# (Used key, Max key, Human Readable Name, text to display when
# describing the quota by default it is 'Used')
types = [("totalInstancesUsed", "maxTotalInstances", _("Instances")),
("totalCoresUsed", "maxTotalCores", _("VCPUs")),
("totalRAMUsed", "maxTotalRAMSize", _("RAM")),
("totalFloatingIpsUsed", "maxTotalFloatingIps",
_("Floating IPs"),
pgettext_lazy('Label in the limit summary', "Allocated")),
("totalSecurityGroupsUsed", "maxSecurityGroups",
_("Security Groups"))]
# Check for volume usage
if 'totalVolumesUsed' in self.usage.limits and self.usage.limits[
'totalVolumesUsed'] >= 0:
types.append(("totalVolumesUsed", "maxTotalVolumes",
_("Volumes")))
types.append(("totalGigabytesUsed", "maxTotalVolumeGigabytes",
_("Volume Storage")))
for t in types:
if t[0] in self.usage.limits and t[1] in self.usage.limits:
for t in CHART_DEFS:
if t.quota_key not in self.usage.limits:
continue
key = t.quota_key
used = self.usage.limits[key]['used']
quota = self.usage.limits[key]['quota']
text = t.used_phrase
if text is None:
text = pgettext_lazy('Label in the limit summary', 'Used')
if len(t) > 3:
text = t[3]
charts.append({
'type': t[0],
'name': t[2],
'used': self.usage.limits[t[0]],
'max': self.usage.limits[t[1]],
'text': text
})
charts.append({
'type': key,
'name': t.label,
'used': used,
'max': quota,
'text': text
})
return charts
def get_context_data(self, **kwargs):