diff --git a/remove_yuyu.sh b/remove_yuyu.sh
index 1ffd4cd..e5ae3d8 100755
--- a/remove_yuyu.sh
+++ b/remove_yuyu.sh
@@ -13,11 +13,13 @@ rm $horizon_path/openstack_dashboard/local/enabled/_6103_admin_billing_price_con
rm $horizon_path/openstack_dashboard/local/enabled/_6104_admin_billing_setting.py
rm $horizon_path/openstack_dashboard/local/enabled/_6104_admin_billing_setting.py
rm $horizon_path/openstack_dashboard/local/enabled/_6105_admin_billing_projects_invoice.py
-rm $horizon_path/openstack_dashboard/local/enabled/_6105_admin_notification_center.py
+rm $horizon_path/openstack_dashboard/local/enabled/_6106_admin_notification_center.py
+rm $horizon_path/openstack_dashboard/local/enabled/_6107_admin_billing_projects_balance.py
rm $horizon_path/openstack_dashboard/local/enabled/_6111_project_billing_panel_group.py
rm $horizon_path/openstack_dashboard/local/enabled/_6112_project_billing_overview.py
rm $horizon_path/openstack_dashboard/local/enabled/_6113_project_billing_usage_cost.py
rm $horizon_path/openstack_dashboard/local/enabled/_6114_project_billing_invoice.py
rm $horizon_path/openstack_dashboard/local/enabled/_6115_project_billing_setting.py
+rm $horizon_path/openstack_dashboard/local/enabled/_6116_project_balance.py
echo "Yuyu Removal Done"
\ No newline at end of file
diff --git a/setup_yuyu.sh b/setup_yuyu.sh
index 8993209..5b49f09 100755
--- a/setup_yuyu.sh
+++ b/setup_yuyu.sh
@@ -14,12 +14,14 @@ ln -sf $root/yuyu/local/enabled/_6104_admin_billing_setting.py $horizon_path/ope
ln -sf $root/yuyu/local/enabled/_6104_admin_billing_setting.py $horizon_path/openstack_dashboard/local/enabled/_6104_admin_billing_setting.py
ln -sf $root/yuyu/local/enabled/_6105_admin_billing_projects_invoice.py $horizon_path/openstack_dashboard/local/enabled/_6105_admin_billing_projects_invoice.py
ln -sf $root/yuyu/local/enabled/_6106_admin_notification_center.py $horizon_path/openstack_dashboard/local/enabled/_6105_admin_notification_center.py
+ln -sf $root/yuyu/local/enabled/_6107_admin_billing_projects_balance.py $horizon_path/openstack_dashboard/local/enabled/_6107_admin_billing_projects_balance.py
ln -sf $root/yuyu/local/enabled/_6111_project_billing_panel_group.py $horizon_path/openstack_dashboard/local/enabled/_6111_project_billing_panel_group.py
ln -sf $root/yuyu/local/enabled/_6112_project_billing_overview.py $horizon_path/openstack_dashboard/local/enabled/_6112_project_billing_overview.py
ln -sf $root/yuyu/local/enabled/_6113_project_billing_usage_cost.py $horizon_path/openstack_dashboard/local/enabled/_6113_project_billing_usage_cost.py
ln -sf $root/yuyu/local/enabled/_6114_project_billing_invoice.py $horizon_path/openstack_dashboard/local/enabled/_6114_project_billing_invoice.py
ln -sf $root/yuyu/local/enabled/_6115_project_billing_setting.py $horizon_path/openstack_dashboard/local/enabled/_6115_project_billing_setting.py
+ln -sf $root/yuyu/local/enabled/_6116_project_balance.py $horizon_path/openstack_dashboard/local/enabled/_6116_project_balance.py
echo "Symlink Creation Done"
echo "Now you can configure and use yuyu dashboard"
\ No newline at end of file
diff --git a/yuyu/admin/billing_setting/forms.py b/yuyu/admin/billing_setting/forms.py
index 5225696..0e162b3 100644
--- a/yuyu/admin/billing_setting/forms.py
+++ b/yuyu/admin/billing_setting/forms.py
@@ -23,6 +23,10 @@ class SettingForm(forms.SelfHandlingForm):
invoice_tax = forms.IntegerField(label=_("INVOICE TAX (%)"),
required=True)
+ invoice_auto_deduct_balance = forms.BooleanField(label=_("Invoice Auto Deduct Balance"),
+ required=False, )
+ how_to_top_up = forms.CharField(label=_("HOW TO TOP UP"),
+ required=True, widget=forms.Textarea())
def clean(self):
data = super(SettingForm, self).clean()
diff --git a/yuyu/admin/billing_setting/templates/billing_setting/_form_setting.html b/yuyu/admin/billing_setting/templates/billing_setting/_form_setting.html
index 856913f..02680bb 100644
--- a/yuyu/admin/billing_setting/templates/billing_setting/_form_setting.html
+++ b/yuyu/admin/billing_setting/templates/billing_setting/_form_setting.html
@@ -8,5 +8,7 @@
{% trans 'Company Logo : A logo that will be used in invoice' %}
{% trans 'Email Admin : Used to send a notification related invoice and error. You can put multiple email here separated by `,` (comma). Example: admin1@company.com,admin2@company.com' %}
{% trans 'Invoice Tax : Tax that will be calculated for each invoice' %}
+
{% trans 'Invoice Auto Deduct Balance : Automatically deduct project balance and finish invoice every end of the month if project balance is sufficient.' %}
+
{% trans 'How To Top Up : Instruction how to top up project balance that can be seen by project user' %}
{% endblock %}
diff --git a/yuyu/admin/projects_balance/__init__.py b/yuyu/admin/projects_balance/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/yuyu/admin/projects_balance/forms.py b/yuyu/admin/projects_balance/forms.py
new file mode 100644
index 0000000..6399f04
--- /dev/null
+++ b/yuyu/admin/projects_balance/forms.py
@@ -0,0 +1,65 @@
+import base64
+
+from django.core import validators
+from django.utils.translation import ugettext_lazy as _
+from djmoney.forms import MoneyField
+
+from horizon import forms, messages, exceptions
+from openstack_dashboard.dashboards.yuyu.cases.balance_use_case import BalanceUseCase
+from openstack_dashboard.dashboards.yuyu.cases.setting_use_case import SettingUseCase
+
+
+class TopUpForm(forms.SelfHandlingForm):
+ USE_CASE = BalanceUseCase()
+
+ amount = MoneyField(label=_("Amount"), min_value=0, max_digits=10)
+ description = forms.CharField(label=_("Description"))
+
+ def __init__(self, request, *args, **kwargs):
+ self.project_id = kwargs['project_id']
+ del kwargs['project_id']
+ super().__init__(request, *args, **kwargs)
+
+ def handle(self, request, data):
+ try:
+ payload = {
+ "amount": data['amount'].amount,
+ "amount_currency": data['amount'].currency.code,
+ 'description': data['description'],
+ }
+
+ result = self.USE_CASE.top_up(request, self.project_id, payload)
+ messages.success(request, _(f"Successfully Top Up"))
+
+ return result
+ except Exception as e:
+ exceptions.handle(request,
+ _('Unable to top up.'))
+
+
+class TopDownForm(forms.SelfHandlingForm):
+ USE_CASE = BalanceUseCase()
+
+ amount = MoneyField(label=_("Amount"), min_value=0, max_digits=10)
+ description = forms.CharField(label=_("Description"))
+
+ def __init__(self, request, *args, **kwargs):
+ self.project_id = kwargs['project_id']
+ del kwargs['project_id']
+ super().__init__(request, *args, **kwargs)
+
+ def handle(self, request, data):
+ try:
+ payload = {
+ "amount": data['amount'].amount,
+ "amount_currency": data['amount'].currency.code,
+ 'description': data['description'],
+ }
+
+ result = self.USE_CASE.top_down(request, self.project_id, payload)
+ messages.success(request, _(f"Successfully Top Down"))
+
+ return result
+ except Exception as e:
+ exceptions.handle(request,
+ _('Unable to top down.'))
diff --git a/yuyu/admin/projects_balance/panel.py b/yuyu/admin/projects_balance/panel.py
new file mode 100644
index 0000000..76acf60
--- /dev/null
+++ b/yuyu/admin/projects_balance/panel.py
@@ -0,0 +1,20 @@
+# 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 ProjectsBalance(horizon.Panel):
+ name = _("Projects Balance")
+ slug = "projects_balance"
diff --git a/yuyu/admin/projects_balance/tables.py b/yuyu/admin/projects_balance/tables.py
new file mode 100644
index 0000000..9dc7c8f
--- /dev/null
+++ b/yuyu/admin/projects_balance/tables.py
@@ -0,0 +1,74 @@
+from django.urls import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import tables
+
+
+class DetailAction(tables.LinkAction):
+ name = "detail"
+ verbose_name = "Detail"
+
+ def get_link_url(self, datum=None, ):
+ return reverse("horizon:admin:projects_balance:balance_detail", kwargs={
+ "project_id": datum['project_id'],
+ })
+
+
+class TopUpAction(tables.LinkAction):
+ name = "top_up"
+ verbose_name = "Top Up"
+ icon = "plus"
+ classes = ("ajax-modal",)
+
+ def get_link_url(self, datum=None, ):
+ return reverse("horizon:admin:projects_balance:top_up", kwargs={
+ "project_id": self.table.kwargs['project_id'],
+ })
+
+
+class TopDownAction(tables.LinkAction):
+ name = "top_down"
+ verbose_name = "Top Down"
+ icon = "minus"
+ classes = ("ajax-modal",)
+
+ def get_link_url(self, datum=None, ):
+ return reverse("horizon:admin:projects_balance:top_down", kwargs={
+ "project_id": self.table.kwargs['project_id'],
+ })
+
+
+class BalanceProjectTable(tables.DataTable):
+ project = tables.WrappingColumn('project', verbose_name=_('Project'))
+ amount = tables.WrappingColumn('amount', verbose_name=_('Amount'))
+
+ def get_object_id(self, datum):
+ return datum['id']
+
+ def get_object_display(self, datum):
+ return datum['project']
+
+ class Meta(object):
+ name = "balance_project_table"
+ hidden_title = True
+ verbose_name = _("Project Balance")
+ row_actions = (DetailAction,)
+
+
+class BalanceTransactionTable(tables.DataTable):
+ date = tables.WrappingColumn('date', verbose_name=_('Date'))
+ amount = tables.WrappingColumn('amount', verbose_name=_('Amount'))
+ action = tables.Column('action', verbose_name=_('Action'))
+ description = tables.Column('description', verbose_name=_('Description'))
+
+ def get_object_id(self, datum):
+ return datum['id']
+
+ def get_object_display(self, datum):
+ return datum['date']
+
+ class Meta(object):
+ name = "balance_table"
+ hidden_title = True
+ verbose_name = _("Balance Transaction")
+ table_actions = (TopUpAction, TopDownAction,)
diff --git a/yuyu/admin/projects_balance/templates/projects_balance/_form_top_down.html b/yuyu/admin/projects_balance/templates/projects_balance/_form_top_down.html
new file mode 100644
index 0000000..2c1888c
--- /dev/null
+++ b/yuyu/admin/projects_balance/templates/projects_balance/_form_top_down.html
@@ -0,0 +1,9 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
+
+{% block modal-body-right %}
+
{% trans "Description:" %}
+
{% trans 'Top down balance for current project' %}
\ No newline at end of file
diff --git a/yuyu/admin/projects_invoice/views.py b/yuyu/admin/projects_invoice/views.py
index 83cf84f..118a389 100644
--- a/yuyu/admin/projects_invoice/views.py
+++ b/yuyu/admin/projects_invoice/views.py
@@ -27,6 +27,7 @@ from horizon import views
from openstack_dashboard import api
from openstack_dashboard.dashboards.yuyu.cases.invoice_use_case import InvoiceUseCase
from .tables import InvoiceTable
+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
@@ -42,7 +43,7 @@ class IndexView(tables.DataTableView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- context['project_list'], _ = api.keystone.tenant_list(self.request, user=self.request.user.id)
+ context['project_list'], _ = api.keystone.tenant_list(self.request)
context['current_project_id'] = self.request.GET.get('project_id', self.request.user.project_id)
context['current_project_name'] = self.request.GET.get('project_name', self.request.user.project_id)
return context
@@ -71,6 +72,7 @@ class InvoiceView(views.APIView):
invoice_uc = InvoiceUseCase()
setting_uc = SettingUseCase()
+ balance_uc = BalanceUseCase()
def get_template_names(self):
if self.request.GET.get('print', None):
@@ -81,8 +83,11 @@ class InvoiceView(views.APIView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
invoice = self.invoice_uc.get_invoice(self.request, self.kwargs['id'], tenant_id=self.kwargs['project_id'])
+ balance = self.balance_uc.retrieve_by_project(self.request, self.kwargs['project_id'])
context['invoice'] = invoice
context['setting'] = self.setting_uc.get_settings(self.request)
+ context['project_balance_amount'] = Money(amount=balance['amount'],
+ currency=balance['amount_currency']) if balance else 0
context['instance_cost'] = self.get_sum_price(invoice, 'instances')
context['volume_cost'] = self.get_sum_price(invoice, 'volumes')
context['fip_cost'] = self.get_sum_price(invoice, 'floating_ips')
@@ -108,6 +113,7 @@ class UsageCostView(tables.MultiTableView):
template_name = "admin/projects_invoice/cost_tables.html"
invoice_uc = InvoiceUseCase()
+ balance_uc = BalanceUseCase()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -119,7 +125,10 @@ class UsageCostView(tables.MultiTableView):
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
def _get_flavor_name(self, flavor_id):
@@ -257,7 +266,9 @@ class FinishInvoice(views.APIView):
invoice_uc = InvoiceUseCase()
def get(self, request, *args, **kwargs):
- self.invoice_uc.finish_invoice(request, kwargs['id'])
+ skip_balance = request.GET.get('skip_balance', '')
+ self.invoice_uc.\
+ finish_invoice(request, kwargs['id'], skip_balance)
next_url = request.GET.get('next', reverse('horizon:admin:projects_invoice:index'))
return HttpResponseRedirect(next_url)
@@ -266,6 +277,7 @@ class RollbackToUnpaidInvoice(views.APIView):
invoice_uc = InvoiceUseCase()
def get(self, request, *args, **kwargs):
- self.invoice_uc.rollback_to_unpaid_invoice(request, kwargs['id'])
+ skip_balance = request.GET.get('skip_balance', '')
+ self.invoice_uc.rollback_to_unpaid_invoice(request, kwargs['id'], skip_balance)
next_url = request.GET.get('next', reverse('horizon:admin:projects_invoice:index'))
return HttpResponseRedirect(next_url)
diff --git a/yuyu/cases/balance_use_case.py b/yuyu/cases/balance_use_case.py
new file mode 100644
index 0000000..60475ae
--- /dev/null
+++ b/yuyu/cases/balance_use_case.py
@@ -0,0 +1,34 @@
+from openstack_dashboard.dashboards.yuyu.core import yuyu_client
+
+
+class BalanceUseCase():
+ def list(self, request):
+ response = yuyu_client.get(request, f"balance/")
+
+ return response.json()
+
+ def retrieve_by_project(self, request, tenant_id=None):
+ if not tenant_id:
+ tenant_id = request.user.project_id
+ response = yuyu_client.get(request, f"balance/{tenant_id}/retrieve_by_project/")
+
+ if response.status_code == 404:
+ return None
+ return response.json()
+
+ def transaction_by_project(self, request, tenant_id=None):
+ if not tenant_id:
+ tenant_id = request.user.project_id
+ response = yuyu_client.get(request, f"balance/{tenant_id}/transaction_by_project/")
+
+ if response.status_code == 404:
+ return []
+ return response.json()
+
+ def top_up(self, request, tenant_id, payload):
+ response = yuyu_client.post(request, f"balance/{tenant_id}/top_up_by_project/", payload)
+ return response.json()
+
+ def top_down(self, request, tenant_id, payload):
+ response = yuyu_client.post(request, f"balance/{tenant_id}/top_down_by_project/", payload)
+ return response.json()
diff --git a/yuyu/cases/invoice_use_case.py b/yuyu/cases/invoice_use_case.py
index 5177b84..8e7c0d4 100644
--- a/yuyu/cases/invoice_use_case.py
+++ b/yuyu/cases/invoice_use_case.py
@@ -110,12 +110,12 @@ class InvoiceUseCase:
def reset_billing(self, request):
yuyu_client.post(request, f"invoice/reset_billing/", {})
- def finish_invoice(self, request, id):
- response = yuyu_client.get(request, f"invoice/{id}/finish/")
+ def finish_invoice(self, request, id, skip_balance):
+ response = yuyu_client.get(request, f"invoice/{id}/finish/?skip_balance={skip_balance}")
data = response.json()
return data
- def rollback_to_unpaid_invoice(self, request, id):
- response = yuyu_client.get(request, f"invoice/{id}/rollback_to_unpaid/")
+ def rollback_to_unpaid_invoice(self, request, id, skip_balance):
+ response = yuyu_client.get(request, f"invoice/{id}/rollback_to_unpaid/?skip_balance={skip_balance}")
data = response.json()
return data
\ No newline at end of file
diff --git a/yuyu/cases/setting_use_case.py b/yuyu/cases/setting_use_case.py
index 71bb9d4..9790e75 100644
--- a/yuyu/cases/setting_use_case.py
+++ b/yuyu/cases/setting_use_case.py
@@ -17,6 +17,11 @@ class SettingUseCase:
return response
+ def get_setting(self, request, key):
+ response = yuyu_client.get(request, f"settings/{key}").json()
+
+ return response
+
def set_setting(self, request, key, value):
return yuyu_client.patch(request, f"settings/{key}/", {
"value": value
diff --git a/yuyu/core/billing_setting/tables.py b/yuyu/core/billing_setting/tables.py
index 3974658..9ad6f56 100644
--- a/yuyu/core/billing_setting/tables.py
+++ b/yuyu/core/billing_setting/tables.py
@@ -12,7 +12,8 @@ class SettingName:
"company_logo": _("Company Logo"),
"company_address": _("Company Address"),
"email_admin": _("Email Admin"),
- "email_notification": _("Email Notification")
+ "email_notification": _("Email Notification"),
+ 'how_to_top_up': _("How To Top Up"),
}
def get_setting_name(self, setting):
diff --git a/yuyu/local/enabled/_6107_admin_billing_projects_balance.py b/yuyu/local/enabled/_6107_admin_billing_projects_balance.py
new file mode 100644
index 0000000..71901c3
--- /dev/null
+++ b/yuyu/local/enabled/_6107_admin_billing_projects_balance.py
@@ -0,0 +1,9 @@
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'projects_balance'
+# The slug of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'admin'
+# The slug of the panel group the PANEL is associated with.
+PANEL_GROUP = 'billing'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = 'openstack_dashboard.dashboards.yuyu.admin.projects_balance.panel.ProjectsBalance'
diff --git a/yuyu/local/enabled/_6116_project_balance.py b/yuyu/local/enabled/_6116_project_balance.py
new file mode 100644
index 0000000..da2ab8e
--- /dev/null
+++ b/yuyu/local/enabled/_6116_project_balance.py
@@ -0,0 +1,10 @@
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'balance'
+# The slug of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'project'
+# The slug of the panel group the PANEL is associated with.
+PANEL_GROUP = 'billing'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = 'openstack_dashboard.dashboards.yuyu.project.balance.panel.Balance'
+# ADD_PANEL = 'openstack_dashboard.dashboards.yuyu.project.invoice.panel.Invoice'
diff --git a/yuyu/project/balance/__init__.py b/yuyu/project/balance/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/yuyu/project/balance/panel.py b/yuyu/project/balance/panel.py
new file mode 100644
index 0000000..fcf3840
--- /dev/null
+++ b/yuyu/project/balance/panel.py
@@ -0,0 +1,20 @@
+# 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 Balance(horizon.Panel):
+ name = _("Balance")
+ slug = "balance"
diff --git a/yuyu/project/balance/tables.py b/yuyu/project/balance/tables.py
new file mode 100644
index 0000000..7e6e832
--- /dev/null
+++ b/yuyu/project/balance/tables.py
@@ -0,0 +1,21 @@
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import tables
+
+
+class BalanceTransactionTable(tables.DataTable):
+ date = tables.WrappingColumn('date', verbose_name=_('Date'))
+ amount = tables.WrappingColumn('amount', verbose_name=_('Amount'))
+ action = tables.Column('action', verbose_name=_('Action'))
+ description = tables.Column('description', verbose_name=_('Description'))
+
+ def get_object_id(self, datum):
+ return datum['id']
+
+ def get_object_display(self, datum):
+ return datum['date']
+
+ class Meta(object):
+ name = "balance_table"
+ hidden_title = True
+ verbose_name = _("Balance Transaction")
diff --git a/yuyu/project/balance/templates/balance/balance_table.html b/yuyu/project/balance/templates/balance/balance_table.html
new file mode 100644
index 0000000..6d44cb0
--- /dev/null
+++ b/yuyu/project/balance/templates/balance/balance_table.html
@@ -0,0 +1,33 @@
+{% extends 'base.html' %}
+{% block title %}{{ page_title }}{% endblock %}
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Project Balance") %}
+{% endblock page_header %}
+
+{% block main %}
+
Current Balance: {{ amount }}
+
+
+
+
+
+
How To Top Up?
+
+
+ {{ how_to_top_up.how_to_top_up }}
+
+
+
+
+
+
+
+
+
Balance History
+ {{ table.render }}
+{% endblock %}
+{% block js %}
+ {{ block.super }}
+{% endblock %}
\ No newline at end of file
diff --git a/yuyu/project/balance/urls.py b/yuyu/project/balance/urls.py
new file mode 100644
index 0000000..f061796
--- /dev/null
+++ b/yuyu/project/balance/urls.py
@@ -0,0 +1,19 @@
+# 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 openstack_dashboard.dashboards.yuyu.project.balance import views
+
+urlpatterns = [
+ url(r'^$', views.IndexView.as_view(), name='index'),
+]
diff --git a/yuyu/project/balance/views.py b/yuyu/project/balance/views.py
new file mode 100644
index 0000000..844afe8
--- /dev/null
+++ b/yuyu/project/balance/views.py
@@ -0,0 +1,51 @@
+# 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 dateutil.parser
+from django.utils import formats
+from django.utils.translation import ugettext_lazy as _
+from djmoney.money import Money
+
+from horizon import exceptions
+from horizon import tables
+from .tables import BalanceTransactionTable
+from ...cases.balance_use_case import BalanceUseCase
+from ...cases.setting_use_case import SettingUseCase
+
+
+class IndexView(tables.DataTableView):
+ table_class = BalanceTransactionTable
+ page_title = _("Balance")
+ template_name = "project/balance/balance_table.html"
+
+ balance_uc = BalanceUseCase()
+ setting_uc = SettingUseCase()
+
+ def get_context_data(self, **kwargs):
+ balance = self.balance_uc.retrieve_by_project(self.request)
+
+ context = super().get_context_data(**kwargs)
+ context['amount'] = Money(amount=balance['amount'], currency=balance['amount_currency']) if balance else 0
+ context['how_to_top_up'] = self.setting_uc.get_setting(self.request, 'how_to_top_up')
+ return context
+
+ def get_data(self):
+ data = []
+
+ for d in self.balance_uc.transaction_by_project(self.request):
+ data.append({
+ 'id': d['id'],
+ 'date': formats.date_format(dateutil.parser.isoparse(d['created_at']), 'd M Y H:m'),
+ 'amount': Money(amount=d['amount'], currency=d['amount_currency']),
+ 'action': d['action'],
+ 'description': d['description'],
+ })
+ return list(data)