Quota Update Panel

Allows viewing of current quota value, quota sizes and percentage
of use of current quota.
Allows changing of each region's quota.

Change-Id: Ia9f254ffb905b4e8971d84f85aefad164e8a3438
This commit is contained in:
Amelia Cordwell 2017-04-18 16:26:32 +12:00 committed by Adrian Turjak
parent 502e6db24d
commit 519c30d185
14 changed files with 857 additions and 2 deletions

View File

@ -17,10 +17,12 @@ import json
import logging
import requests
from six.moves.urllib.parse import urljoin
import six
from django.conf import settings
from horizon.utils import functions as utils
from horizon.utils import memoized
from openstack_dashboard.api import base
@ -37,6 +39,68 @@ TASK = collections.namedtuple('Task',
'created_on', 'approved_on', 'page',
'completed_on', 'actions', 'status'])
QUOTA_SIZE = collections.namedtuple('QuotaSize',
['id', 'name', 'cinder',
'nova', 'neutron'])
REGION_QUOTA = collections.namedtuple('RegionQuota',
['id', 'region',
'quota_size', 'preapproved_quotas'])
REGION_QUOTA_VALUE = collections.namedtuple('RegionQuotaValue',
['id', 'name',
'service', 'current_quota',
'current_usage', 'percent',
'size_blob', 'important'])
SIZE_QUOTA_VALUE = collections.namedtuple('SizeQuotaValue',
['id', 'name', 'service',
'value', 'current_quota',
'current_usage', 'percent'])
QUOTA_TASK = collections.namedtuple(
'QuotaTask',
['id', 'regions', 'size', 'user', 'created', 'valid', 'status'])
# NOTE(amelia): A list of quota names that we consider to be the most
# relevant to customers to be shown initially on the update page.
# These can be overriden in the local_settings file:
# IMPORTANT_QUOTAS = {<service>: [<quota_name>], }
DEFAULT_IMPORTANT_QUOTAS = {
'nova': [
'instances', 'cores', 'ram',
],
'cinder': [
'volumes', 'snapshots', 'gigabytes',
],
'neutron': [
'network', 'floatingip', 'router', 'security_group',
],
}
# NOTE(adriant): Quotas that should be hidden by default.
# Can be overriden in the local_settings file by setting:
# HIDDEN_QUOTAS = {<service>: [<quota_name>], }
# or disabled entirely with: HIDDEN_QUOTAS = {}
DEFAULT_HIDDEN_QUOTAS = {
# these values have long since been deprecated from Nova
'nova': [
'security_groups', 'security_group_rules',
'floating_ips', 'fixed_ips',
],
# these by default have no limit
'cinder': [
'per_volume_gigabytes', 'volumes_lvmdriver-1',
'gigabytes_lvmdriver-1', 'snapshots_lvmdriver-1',
],
'neutron': [
'subnetpool',
],
}
def _get_endpoint_url(request):
# If the request is made by an anonymous user, this endpoint request fails.
@ -287,12 +351,11 @@ def task_list(request, filters={}, page=1):
more = resp['has_more']
for task in resp['tasks']:
tasklist.append(task_obj_get(request, task=task, page=page))
return tasklist, prev, more
except Exception as e:
LOG.error(e)
raise
return tasklist, prev, more
def task_get(request, task_id):
# Get a single task
@ -364,3 +427,190 @@ def task_revalidate(request, task_id):
data.update(action_data)
return task_update(request, task_id, json.dumps(data))
# Quota management functions
def _is_quota_hidden(service, resource):
hidden_quotas = getattr(settings, 'HIDDEN_QUOTAS', None)
if hidden_quotas is None:
hidden_quotas = DEFAULT_HIDDEN_QUOTAS
return service in hidden_quotas and resource in hidden_quotas[service]
def _is_quota_important(service, resource):
important_quotas = getattr(settings, 'IMPORTANT_QUOTAS', None)
if important_quotas is None:
important_quotas = DEFAULT_IMPORTANT_QUOTAS
return (
service in important_quotas and resource in important_quotas[service])
@memoized.memoized_method
def _get_quota_information(request, regions=None):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
params = {}
if regions:
params = {'regions': regions}
try:
return get(request, 'openstack/quotas/',
params=params, headers=headers).json()
except Exception as e:
LOG.error(e)
raise
def quota_sizes_get(request, region=None):
# Gets the list of quota sizes, and a json blob defining what they
# have for each of the services
# Region param is useless here, but nedded for memoized decorator to work
quota_sizes_dict = {}
resp = _get_quota_information(request, regions=region)
for size_name, size in six.iteritems(resp['quota_sizes']):
quota_sizes_dict[size_name] = QUOTA_SIZE(
id=size_name,
name=size_name,
cinder=json.dumps(size['cinder'], indent=1),
nova=json.dumps(size['nova'], indent=1),
neutron=json.dumps(size['neutron'], indent=1),
)
quota_sizes = []
for size in resp['quota_size_order']:
quota_sizes.append(quota_sizes_dict[size])
return quota_sizes
def size_details_get(request, size, region=None):
""" Gets the current details of the size as well as the current region's
quota
"""
quota_details = []
if not region:
region = request.user.services_region
resp = _get_quota_information(request, regions=region)
data = resp['quota_sizes'][size]
region_data = resp['regions'][0]['current_quota']
for service, values in six.iteritems(data):
for resource, value in six.iteritems(values):
if _is_quota_hidden(service, resource):
continue
usage = resp['regions'][0]['current_usage'][service].get(
resource)
try:
percent = float(usage)/value
except TypeError:
percent = '-'
quota_details.append(
SIZE_QUOTA_VALUE(
id=resource,
name=resource,
service=service,
value=value,
current_quota=region_data[service][resource],
current_usage=usage,
percent=percent
)
)
return quota_details
def quota_details_get(request, region):
quota_details = []
resp = _get_quota_information(request, regions=region)
data = resp['regions'][0]['current_quota']
for service, values in six.iteritems(data):
for name, value in six.iteritems(values):
if _is_quota_hidden(service, name):
continue
if value < 0:
value = 'No Limit'
usage = resp['regions'][0]['current_usage'][service].get(name)
try:
percent = float(usage)/value
except TypeError:
percent = '-'
size_blob = {}
for size_name, size_data in resp['quota_sizes'].iteritems():
size_blob[size_name] = size_data[service].get(name, '-')
if name != 'id':
quota_details.append(
REGION_QUOTA_VALUE(
id=name,
name=name,
service=service,
current_quota=value,
current_usage=usage,
percent=percent,
size_blob=size_blob,
important=_is_quota_important(service, name)
)
)
return quota_details
def region_quotas_get(request, region=None):
quota_details = []
resp = _get_quota_information(request, regions=region)
data = resp['regions']
for region_values in data:
quota_details.append(
REGION_QUOTA(
id=region_values['region'],
region=region_values['region'],
quota_size=region_values['current_quota_size'],
preapproved_quotas=', '.join(region_values[
'quota_change_options'])
)
)
return quota_details
def quota_tasks_get(request, region=None):
# Region param only used to help with memoized decorator
quota_tasks = []
resp = _get_quota_information(request, regions=region)
for task in resp['active_quota_tasks']:
quota_tasks.append(
QUOTA_TASK(
id=task['id'],
regions=', '.join(task['regions']),
size=task['size'],
user=task['request_user'],
created=task['task_created'].split("T")[0],
valid=task['valid'],
status=task['status'],
)
)
return quota_tasks
def update_quotas(request, size, regions=[]):
headers = {'Content-Type': 'application/json',
'X-Auth-Token': request.user.token.id}
data = {
'size': size,
}
if regions:
data['regions'] = regions
return post(request, 'openstack/quotas/',
data=json.dumps(data),
headers=headers)

View File

View File

@ -0,0 +1,66 @@
# Copyright 2016 Catalyst IT Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse # noqa
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from adjutant_ui.api import adjutant
LOG = logging.getLogger(__name__)
class UpdateQuotaForm(forms.SelfHandlingForm):
region = forms.CharField(label=_("Region"))
region.widget.attrs['readonly'] = True
size = forms.ChoiceField(label=_("Size"))
size.widget.attrs['onchange'] = 'updateSizeTable()'
failure_url = 'horizon:management:quota:index'
submit_url = 'horizon:management:quota:update'
success_url = "horizon:management:quota:index"
def __init__(self, *args, **kwargs):
size_choices = kwargs.pop('size_choices')
super(UpdateQuotaForm, self).__init__(*args, **kwargs)
self.fields['size'].choices = size_choices
def handle(self, request, data):
try:
response = adjutant.update_quotas(request, data['size'],
regions=[data['region']])
if response.status_code == 200:
messages.success(request, _('Quota updated sucessfully.'))
elif response.status_code == 202:
messages.success(request, _('Task created but requires '
'admin approval.'))
elif response.status_code == 400:
messages.error(request, _('Failed to update quota. You may'
' have usage over the new values '
'that you are attempting to update'
' the quota to.'))
else:
messages.error(request, _('Failed to update quota.'))
return True
except Exception:
msg = _('Failed to update quota.')
url = reverse('horizon:management:quota:index')
exceptions.handle(request, msg, redirect=url)
return False

View File

@ -0,0 +1,23 @@
# Copyright (c) 2016 Catalyst IT Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
class QuotaPanel(horizon.Panel):
name = _("Quota Managment")
slug = 'quota'
policy_rules = (('identity', "identity:project_mod_or_admin"),)

View File

@ -0,0 +1,189 @@
# Copyright 2016 Catalyst IT Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import tables
from openstack_dashboard.dashboards.admin.defaults.tables import get_quota_name
from adjutant_ui.api import adjutant
def to_caps(value):
return value.title()
def display_as_percent(value):
if value == "-":
return value
return '{:.1%}'.format(value)
def service_name(value):
# Takes service names and returns a 'nice' name of where they
# are from
service_name_dict = {'cinder': 'Volume Storage',
'neutron': 'Networking',
'nova': 'Compute'}
return service_name_dict.get(value, value)
class UpdateQuota(tables.LinkAction):
name = "update"
verbose_name = _("Update Quota")
url = "horizon:management:quota:update"
classes = ("ajax-modal",)
icon = "edit"
class CancelQuotaTask(tables.DeleteAction):
help_text = _("This will cancel the selected quota update.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Cancel Quota Update",
u"Cancel Quota Updates",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Cancelled Quota Update",
u"Cancelled Quota Updates",
count
)
def delete(self, request, obj_id):
result = adjutant.task_cancel(request, obj_id)
if not result or result.status_code != 200:
exception = exceptions.NotAvailable()
exception._safe_message = False
raise exception
def allowed(self, request, task=None):
if task:
return task.status == "Awaiting Approval"
return True
class ViewRegion(tables.LinkAction):
name = "view_region"
verbose_name = _("View Region")
url = "horizon:management:quota:region_detail"
class ViewSize(tables.LinkAction):
name = "view_size"
verbose_name = _("View Size")
url = "horizon:management:quota:size_detail"
class UpdateQuotaRow(tables.Row):
def load_cells(self, resource=None):
super(UpdateQuotaRow, self).load_cells(resource)
resource = self.datum
if resource.important is False:
self.attrs['hide'] = True
self.attrs['style'] = 'display: none;'
self.attrs['size_blob'] = json.dumps(self.datum.size_blob)
class RegionQuotaDetailTable(tables.DataTable):
service = tables.Column("service", verbose_name=_("Service"),
filters=(service_name, ))
name = tables.Column(get_quota_name, verbose_name=_("Resource Name"),)
value = tables.Column("current_quota", verbose_name=_("Resource Quota"), )
usage = tables.Column("current_usage", verbose_name=_("Current Usage"))
percent = tables.Column("percent", verbose_name=_("Percentage of Use"),
filters=(display_as_percent, ))
class QuotaDetailUsageTable(tables.DataTable):
service = tables.Column("service", verbose_name=_("Service"),
filters=(service_name, ))
name = tables.Column(get_quota_name, verbose_name=_("Resource Name"),)
value = tables.Column("value", verbose_name=_("Quota Value"), )
current_quota = tables.Column("current_quota",
verbose_name=_("Current Quota "
"(Current Region)"), )
class RegionOverviewTable(tables.DataTable):
region = tables.Column("region", verbose_name=_("Region Name"),
link=("horizon:management:quota:region_detail"))
quota_size = tables.Column("quota_size",
verbose_name=_("Current Quota Size"),
filters=(to_caps, ))
preapproved_quotas = tables.Column(
"preapproved_quotas", filters=(to_caps, ),
verbose_name=_("Preapproved Quota Sizes"))
class Meta(object):
name = "region_overview"
row_actions = (UpdateQuota, ViewRegion)
verbose_name = _("Current Quotas")
hidden_title = False
class QuotaTasksTable(tables.DataTable):
quota_size = tables.Column(
"size",
verbose_name=_("Proposed Size"),
filters=(to_caps, ))
regions = tables.Column("regions", verbose_name=_("For Regions"))
user = tables.Column("user", verbose_name=_("Requested By"))
created = tables.Column("created", verbose_name=_("Requested On"))
valid = tables.Column("valid", verbose_name=_("Valid"))
stats = tables.Column("status", verbose_name=_("Status"))
class Meta(object):
name = "quota_tasks"
row_actions = (CancelQuotaTask, )
verbose_name = _("Previous Quota Changes")
hidden_title = False
class SizeOverviewTable(tables.DataTable):
id = tables.Column("id", hidden=True)
size = tables.Column("name", verbose_name=_("Size Name"),
filters=(to_caps, ))
class Meta(object):
name = "size_overview"
row_actions = (ViewSize, )
verbose_name = _("Quota Sizes")
hidden_title = False
class ChangeSizeDisplayTable(tables.DataTable):
service = tables.Column("service", verbose_name=_("Service"),
filters=(service_name, ),
hidden=True)
name = tables.Column(get_quota_name, verbose_name=_("Resource"),)
current_quota = tables.Column("current_quota",
verbose_name=_("Current Quota"), )
usage = tables.Column("current_usage", verbose_name=_("Current Usage"))
value = tables.Column("value", verbose_name=_("New Quota Value"), )
class Meta(object):
name = 'change_size'
row_class = UpdateQuotaRow

View File

@ -0,0 +1,18 @@
{% load i18n %}
<div class=quota-help>
<p>{% blocktrans trimmed %}
Your current quotas are avaliable here, and can be changed to suit your needs.
{% endblocktrans %}</p>
<p>{% blocktrans trimmed %}
Certain types of quota changes, such as changing your quota more than once
in a given period, or changing your quota by large amounts will require admin
approval. The period and the quota sizes themselves is configured by your
admin, with the default period being 30 days.
{% endblocktrans %}</p>
<p>{% blocktrans trimmed %}
If your proposed change needed approval, you will be emailed on completion.
{% endblocktrans %}</p>
</div>

View File

@ -0,0 +1,62 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}update_quota_form{% endblock %}
{% block form_action %}{% url 'horizon:management:quota:update' region.id %}{% endblock %}
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<a id="toggle_link" onclick='toggleDisplayTable()'>{% trans 'Display all quotas' %}</a>
{{ change_size_table.render }}
<script>
var select = document.getElementById('id_size')
var toggle_link = document.getElementById('toggle_link')
var show_all = false;
var table = document.getElementById("change_size");
var count_headings = table.getElementsByClassName("table_count");
for (i=0; i<count_headings.length; i++){
count_headings[i].parentElement.parentElement.style.display = 'none';
}
function updateSizeTable(){
var current_size = select.options[select.selectedIndex].value;
var rows = document.getElementById("change_size").tBodies[0].rows;
for (i=1; i < rows.length; i++){
var cell = rows[i].cells[4];
var value_dict = $.parseJSON(rows[i].getAttribute('size_blob'));
cell.innerHTML = value_dict[current_size]
}
}
function toggleDisplayTable(){
show_all = !show_all;
if(show_all){
toggle_link.innerHTML = "{% trans 'Hide some rows' %}";
}else{
toggle_link.innerHTML = "{% trans 'Display all quotas' %}";
}
var tr = document.getElementById("change_size").tBodies[0].rows;
for (i=0; i<tr.length; i++){
if (tr[i].getAttribute('hide') == ""){
if (show_all){
tr[i].style.display = '';
} else {
tr[i].style.display = 'none';
}
}
}
}
updateSizeTable()
</script>
</div>
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Quotas" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Quotas") %}
{% endblock page_header %}
{% block main %}
<div class="left">
{% include 'management/quota/_index_help.html' %}
<div id="region-overview">
{{ region_overview_table.render }}
</div>
<div id="quota-tasks-overview">
{{ quota_tasks_table.render }}
</div>
</div>
<div class="right">
<div id="size-overview">
{{ size_overview_table.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %} {% trans "Quota Details" %} {% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %} {{title}} - Quota Details{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=title %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Quota" %}{% endblock %}
{% block main %}
{% include 'management/quota/_update.html' %}
{% endblock %}

View File

@ -0,0 +1,28 @@
# Copyright (c) 2016 Catalyst IT Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls import url
from adjutant_ui.content.quota import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^region/(?P<region>[^/]+)$', views.RegionDetailView.as_view(),
name='region_detail'),
url(r'^(?P<region>[^/]+)/update$',
views.RegionUpdateView.as_view(), name='update'),
url(r'^size/(?P<size>[^/]+)$', views.QuotaSizeView.as_view(),
name='size_detail'),
]

View File

@ -0,0 +1,159 @@
# Copyright (c) 2016 Catalyst IT Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tables as horizon_tables
from adjutant_ui.content.quota import forms as quota_forms
from adjutant_ui.content.quota import tables as quota_tables
from adjutant_ui.api import adjutant
class IndexView(horizon_tables.MultiTableView):
page_title = _("Quota Management")
table_classes = (quota_tables.RegionOverviewTable,
quota_tables.SizeOverviewTable,
quota_tables.QuotaTasksTable)
template_name = 'management/quota/index.html'
def get_region_overview_data(self):
try:
return adjutant.region_quotas_get(self.request)
except Exception:
exceptions.handle(self.request, _('Failed to list quota sizes.'))
return []
def get_size_overview_data(self):
try:
return adjutant.quota_sizes_get(self.request)
except Exception:
exceptions.handle(self.request, _('Failed to list quota sizes.'))
return []
def get_quota_tasks_data(self):
try:
return adjutant.quota_tasks_get(self.request)
except Exception:
exceptions.handle(self.request, _('Failed to list quota tasks.'))
return []
class RegionDetailView(horizon_tables.DataTableView):
table_class = quota_tables.RegionQuotaDetailTable
template_name = 'management/quota/region_detail.html'
page_title = _("'{{ region }}' Quota Details")
def get_data(self):
try:
return adjutant.quota_details_get(self.request,
self.kwargs['region'])
except Exception:
exceptions.handle(self.request, _('Failed to list quota sizes.'))
return []
def get_context_data(self, **kwargs):
context = super(RegionDetailView, self).get_context_data(**kwargs)
context['region'] = self.kwargs['region']
return context
class QuotaSizeView(horizon_tables.DataTableView):
table_class = quota_tables.QuotaDetailUsageTable
template_name = 'management/quota/size_detail.html'
page_title = _("'{{ size }}' Quota Details")
def get_data(self):
try:
return adjutant.size_details_get(self.request,
size=self.kwargs['size'])
except Exception:
exceptions.handle(self.request, _('Failed to list quota size.'))
return []
def get_context_data(self, **kwargs):
# request.user.services_region
context = super(QuotaSizeView, self).get_context_data(**kwargs)
context['title'] = _("%s - Quota Details") \
% self.kwargs['size'].title()
return context
class RegionUpdateView(forms.ModalFormView, horizon_tables.MultiTableView):
form_class = quota_forms.UpdateQuotaForm
table_classes = (quota_tables.ChangeSizeDisplayTable, )
submit_url = 'horizon:management:quota:update'
context_object_name = 'ticket'
template_name = 'management/quota/update.html'
success_url = reverse_lazy("horizon:management:quota:index")
page_title = _("Update Quota")
def get_change_size_data(self):
try:
return adjutant.quota_details_get(self.request,
region=self.kwargs['region'])
except Exception:
exceptions.handle(self.request, _('Failed to list quota sizes.'))
return []
def get_object(self):
return adjutant.region_quotas_get(self.request,
region=self.kwargs['region'])[0]
def get_context_data(self, **kwargs):
context = super(RegionUpdateView, self).get_context_data(**kwargs)
context['region'] = self.get_object()
args = (self.kwargs['region'],)
context['submit_url'] = reverse(self.submit_url, args=args)
context['form'] = self.get_form()
return context
def get_form_kwargs(self):
kwargs = super(RegionUpdateView, self).get_form_kwargs()
sizes = adjutant.quota_sizes_get(
self.request, region=self.kwargs['region'])
kwargs['size_choices'] = []
region = self.get_object()
for size in sizes:
if region.quota_size == size.name:
continue
if size.name not in region.preapproved_quotas:
kwargs['size_choices'].append(
[size.id, "%s (requires approval)" % size.name.title()])
else:
kwargs['size_choices'].append([size.id, size.name.title()])
return kwargs
def get_initial(self):
region = self.get_object()
data = {'id': region.id,
'region': region.region,
'quota_size': region.quota_size,
'preapproved_quotas': region.preapproved_quotas
}
return data
def post(self, request, *args, **kwargs):
# NOTE(amelia): The multitableview overides the form view post
# this reinstates it.
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)

View File

@ -0,0 +1,9 @@
# The slug of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'quota'
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'management'
PANEL_GROUP = 'default'
# Python panel class of the PANEL to be added.
ADD_PANEL = 'adjutant_ui.content.quota.panel.QuotaPanel'