diff --git a/yuyu/admin/projects_invoice/templates/projects_invoice/cost_tabs.html b/yuyu/admin/projects_invoice/templates/projects_invoice/cost_tabs.html new file mode 100644 index 0000000..e7c51db --- /dev/null +++ b/yuyu/admin/projects_invoice/templates/projects_invoice/cost_tabs.html @@ -0,0 +1,114 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Usage Cost" %}{% endblock %} + +{% block main %} + {% include "admin/price_configuration/missing_prices.html" %} + {% if invoice %} + +
+
+
+ Invoice Month {{ invoice.start_date|date:"M Y" }} +
+
+ + {% include 'admin/projects_invoice/manage_invoice_modal.html' with invoice=invoice project_balance_amount=project_balance_amount %} + + +
+
+ +
+
+
+
+

Invoice State

+
+
+

{{ invoice.state_text }}

+
+
+
+
+
+
+

Subtotal

+
+
+

{{ invoice.subtotal_money }}

+
+
+
+
+ + {% if invoice.state != 1 %} +
+
+
+
+

Tax

+
+
+

{{ invoice.tax_money }}

+
+
+
+
+
+
+

Total

+
+
+

{{ invoice.total_money }}

+
+
+
+
+ {% endif %} + +
+
+ {{ tab_group.render }} +
+
+ +
+ {% else %} +

Billing not enabled or you don't have any usage yet


+ {% endif %} +{% endblock %} + +{% block js %} + {{ block.super }} + + + +{% endblock %} diff --git a/yuyu/admin/projects_invoice/urls.py b/yuyu/admin/projects_invoice/urls.py index 13ea78d..18cf879 100644 --- a/yuyu/admin/projects_invoice/urls.py +++ b/yuyu/admin/projects_invoice/urls.py @@ -14,10 +14,11 @@ from django.conf.urls import url from openstack_dashboard.dashboards.yuyu.admin.projects_invoice import views + urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^invoice/detail/(?P[^/]+)/(?P[^/]+)/$', views.InvoiceView.as_view(), name='invoice_detail'), - url(r'^invoice/usage/(?P[^/]+)/(?P[^/]+)/$', views.UsageCostView.as_view(), name='usage_cost'), + url(r'^invoice/usage/(?P[^/]+)/(?P[^/]+)/$', views.UsageCostTabView.as_view(), name='usage_cost'), url(r'^invoice/finish/(?P[^/]+)/$', views.FinishInvoice.as_view(), name='finish_invoice'), url(r'^invoice/rollback_to_unpaid/(?P[^/]+)/$', views.RollbackToUnpaidInvoice.as_view(), name='rollback_to_unpaid'), diff --git a/yuyu/admin/projects_invoice/views.py b/yuyu/admin/projects_invoice/views.py index 118a389..932c894 100644 --- a/yuyu/admin/projects_invoice/views.py +++ b/yuyu/admin/projects_invoice/views.py @@ -24,6 +24,8 @@ from djmoney.money import Money from horizon import exceptions from horizon import tables from horizon import views +from horizon import tabs + from openstack_dashboard import api from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase from .tables import InvoiceTable @@ -31,6 +33,7 @@ from ...cases.balance_use_case import BalanceUseCase from ...cases.setting_use_case import SettingUseCase from ...core.usage_cost.tables import InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, \ SnapshotCostTable, ImageCostTable +from ...core.usage_cost.tabs import UsageCostTabs from ...core.utils.invoice_utils import state_to_text @@ -106,6 +109,31 @@ class InvoiceView(views.APIView): return sum(instance_prices) +class UsageCostTabView(tabs.TabbedTableView): + tab_group_class = UsageCostTabs + page_title = _("Usage Cost") + template_name = "admin/projects_invoice/cost_tabs.html" + + invoice_uc = InvoiceUseCase() + balance_uc = BalanceUseCase() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def get(self, request, *args, **kwargs): + request.invoice = self.invoice_uc.get_invoice(self.request, self.kwargs['id'], + tenant_id=self.kwargs['project_id']) + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + balance = self.balance_uc.retrieve_by_project(self.request, self.kwargs['project_id']) + context['invoice'] = self.request.invoice + context['project_balance_amount'] = Money(amount=balance['amount'], + currency=balance['amount_currency']) if balance else 0 + return context + + class UsageCostView(tables.MultiTableView): table_classes = ( InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable) diff --git a/yuyu/core/usage_cost/tabs.py b/yuyu/core/usage_cost/tabs.py new file mode 100644 index 0000000..a511181 --- /dev/null +++ b/yuyu/core/usage_cost/tabs.py @@ -0,0 +1,181 @@ +import dateutil.parser +from django.utils.timesince import timesince +from django.utils.translation import ugettext_lazy as _ +from djmoney.money import Money + +from horizon import exceptions, tabs +from openstack_dashboard import api +from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import ( + InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable +) + + +class InstanceTab(tabs.TableTab): + table_classes = (InstanceCostTable,) + name = _("Instance") + slug = "instance" + template_name = 'horizon/common/_detail_table.html' + + def _get_flavor_name(self, flavor_id): + try: + return api.nova.flavor_get(self.request, flavor_id).name + except Exception: + return 'Invalid Flavor' + + def get_instance_cost_data(self): + try: + datas = map(lambda x: { + "id": x['id'], + "name": x['name'], + "flavor": self._get_flavor_name(x['flavor_id']), + "usage": timesince( + dateutil.parser.isoparse(x['start_date']), + dateutil.parser.isoparse(x['adjusted_end_date']) + ), + "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency']) + }, self.request.invoice.get('instances', [])) + return datas + except Exception: + error_message = _('Unable to get instance cost') + exceptions.handle(self.request, error_message) + return [] + + +class VolumeTab(tabs.TableTab): + table_classes = (VolumeCostTable,) + name = _("Volume") + slug = "volume" + template_name = 'horizon/common/_detail_table.html' + + def _get_volume_name(self, volume_type_id): + try: + return api.cinder.volume_type_get(self.request, volume_type_id).name + except Exception: + return 'Invalid Volume' + + def get_volume_cost_data(self): + try: + datas = map(lambda x: { + "id": x['id'], + "name": x['volume_name'], + "usage": timesince( + dateutil.parser.isoparse(x['start_date']), + dateutil.parser.isoparse(x['adjusted_end_date']) + ), + 'type': self._get_volume_name(x['volume_type_id']), + 'size': x['space_allocation_gb'], + "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency']) + }, self.request.invoice.get('volumes', [])) + return datas + except Exception: + error_message = _('Unable to get volume cost') + exceptions.handle(self.request, error_message) + return [] + + +class FloatingIpTab(tabs.TableTab): + table_classes = (FloatingIpCostTable,) + name = _("Floating IP") + slug = "floating_ip" + template_name = 'horizon/common/_detail_table.html' + + def get_floating_ip_cost_data(self): + try: + datas = map(lambda x: { + "id": x['id'], + "name": x['ip'], + "usage": timesince( + dateutil.parser.isoparse(x['start_date']), + dateutil.parser.isoparse(x['adjusted_end_date']) + ), + "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency']) + }, self.request.invoice.get('floating_ips', [])) + + return datas + except Exception: + error_message = _('Unable to get floating ip cost') + exceptions.handle(self.request, error_message) + return [] + + +class RouterTab(tabs.TableTab): + table_classes = (RouterCostTable,) + name = _("Router") + slug = "router" + template_name = 'horizon/common/_detail_table.html' + + def get_router_cost_data(self): + try: + datas = map(lambda x: { + "id": x['id'], + "name": x['name'], + "usage": timesince( + dateutil.parser.isoparse(x['start_date']), + dateutil.parser.isoparse(x['adjusted_end_date']) + ), + "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency']) + }, self.request.invoice.get('routers', [])) + + return datas + except Exception: + error_message = _('Unable to get router cost') + exceptions.handle(self.request, error_message) + return [] + + +class SnapshotTab(tabs.TableTab): + table_classes = (SnapshotCostTable,) + name = _("Snapshot") + slug = "snapshot" + template_name = 'horizon/common/_detail_table.html' + + def get_snapshot_cost_data(self): + try: + datas = map(lambda x: { + "id": x['id'], + "name": x['name'], + 'size': x['space_allocation_gb'], + "usage": timesince( + dateutil.parser.isoparse(x['start_date']), + dateutil.parser.isoparse(x['adjusted_end_date']) + ), + "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency']) + }, self.request.invoice.get('snapshots', [])) + + return datas + except Exception: + error_message = _('Unable to get snapshot cost') + exceptions.handle(self.request, error_message) + return [] + + +class ImageTab(tabs.TableTab): + table_classes = (ImageCostTable,) + name = _("Image") + slug = "image" + template_name = 'horizon/common/_detail_table.html' + + def get_image_cost_data(self): + try: + datas = map(lambda x: { + "id": x['id'], + "name": x['name'], + 'size': x['space_allocation_gb'], + "usage": timesince( + dateutil.parser.isoparse(x['start_date']), + dateutil.parser.isoparse(x['adjusted_end_date']) + ), + "cost": Money(amount=x['price_charged'], currency=x['price_charged_currency']) + }, self.request.invoice.get('images', [])) + + return datas + except Exception: + error_message = _('Unable to get images cost') + exceptions.handle(self.request, error_message) + return [] + + +class UsageCostTabs(tabs.TabGroup): + slug = "usage_cost" + tabs = (InstanceTab, VolumeTab, FloatingIpTab, RouterTab, SnapshotTab, ImageTab, ) + sticky = True diff --git a/yuyu/project/usage_cost/templates/usage_cost/index_tabs.html b/yuyu/project/usage_cost/templates/usage_cost/index_tabs.html new file mode 100644 index 0000000..7aa9036 --- /dev/null +++ b/yuyu/project/usage_cost/templates/usage_cost/index_tabs.html @@ -0,0 +1,122 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Usage Cost" %}{% endblock %} + +{% block main %} + {% include "admin/price_configuration/missing_prices.html" %} + {% if invoice %} +
+
+
+ + Invoice Month + +
+
+ +
+
+ +
+
+
+
+

Invoice State

+
+
+

{{ invoice.state_text }}

+
+
+
+
+
+
+

Subtotal

+
+
+

{{ invoice.subtotal_money }}

+
+
+
+
+ + {% if invoice.state != 1 %} +
+
+
+
+

Tax

+
+
+

{{ invoice.tax_money }}

+
+
+
+
+
+
+

Total

+
+
+

{{ invoice.total_money }}

+
+
+
+
+ {% endif %} + +
+
+ {{ tab_group.render }} +
+
+ +
+ {% else %} +

You don't have any usage yet


+ {% endif %} +{% endblock %} + +{% block js %} + {{ block.super }} + + + +{% endblock %} diff --git a/yuyu/project/usage_cost/urls.py b/yuyu/project/usage_cost/urls.py index 3892a28..b57a22b 100644 --- a/yuyu/project/usage_cost/urls.py +++ b/yuyu/project/usage_cost/urls.py @@ -16,5 +16,5 @@ from openstack_dashboard.dashboards.yuyu.project.usage_cost import views urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^$', views.IndexViewTab.as_view(), name='index'), ] diff --git a/yuyu/project/usage_cost/views.py b/yuyu/project/usage_cost/views.py index d9bda72..2953609 100644 --- a/yuyu/project/usage_cost/views.py +++ b/yuyu/project/usage_cost/views.py @@ -14,14 +14,43 @@ from django.utils.timesince import timesince from django.utils.translation import ugettext_lazy as _ from djmoney.money import Money -from horizon import exceptions -from horizon import tables +from horizon import exceptions, tables, tabs from openstack_dashboard import api from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase from openstack_dashboard.dashboards.yuyu.core.usage_cost.tables import InstanceCostTable, VolumeCostTable, \ FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable +from openstack_dashboard.dashboards.yuyu.core.usage_cost.tabs import UsageCostTabs +class IndexViewTab(tabs.TabbedTableView): + + tab_group_class = UsageCostTabs + page_title = _("Usage Cost") + template_name = "project/usage_cost/index_tabs.html" + + invoice_uc = InvoiceUseCase() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def get(self, request, *args, **kwargs): + request.invoice_list = self.invoice_uc.get_simple_list(self.request) + request.has_invoice = len(request.invoice_list) != 0 + + invoice_id = self.request.GET.get('invoice_id', None) + if not invoice_id and request.has_invoice: + invoice_id = request.invoice_list[0]['id'] + + request.invoice = self.invoice_uc.get_invoice(self.request, invoice_id) if request.has_invoice else {} + return super().get(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['invoice_list'] = self.request.invoice_list + context['invoice'] = self.request.invoice + return context + + class IndexView(tables.MultiTableView): table_classes = ( InstanceCostTable, VolumeCostTable, FloatingIpCostTable, RouterCostTable, SnapshotCostTable, ImageCostTable)