Merge pull request #220 from Modulos/vm_bill

Vm bill
This commit is contained in:
Levi Velázquez 2017-05-08 22:10:50 -05:00 committed by GitHub
commit 7cd2f1884d
12 changed files with 371 additions and 5 deletions

View file

@ -5,7 +5,8 @@ from django.core.urlresolvers import reverse
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from .forms import HostingOrderAdminForm from .forms import HostingOrderAdminForm
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, ManageVM from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, \
ManageVM, HostingBill
from .opennebula_functions import HostingManageVMAdmin from .opennebula_functions import HostingManageVMAdmin
@ -98,3 +99,4 @@ admin.site.register(HostingOrder, HostingOrderAdmin)
admin.site.register(VirtualMachineType) admin.site.register(VirtualMachineType)
admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin) admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin)
admin.site.register(ManageVM, HostingManageVMAdmin) admin.site.register(ManageVM, HostingManageVMAdmin)
admin.site.register(HostingBill)

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-24 04:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0027_auto_20160711_0210'),
]
operations = [
migrations.CreateModel(
name='ManageVMs',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
},
),
]

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-24 04:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0028_managevms'),
]
operations = [
migrations.CreateModel(
name='ManageVM',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
},
),
]

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-05 11:50
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import utils.mixins
class Migration(migrations.Migration):
dependencies = [
('utils', '0005_auto_20170322_1443'),
('membership', '0006_auto_20160526_0445'),
('hosting', '0029_managevm'),
]
operations = [
migrations.CreateModel(
name='HostingBill',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')),
],
options={
'permissions': (('view_hostingbill', 'View Hosting Bill'),),
},
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 12:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0030_hostingbill'),
]
operations = [
migrations.AddField(
model_name='hostingbill',
name='total_price',
field=models.FloatField(default=0.0),
),
]

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-07 04:49
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hosting', '0031_hostingbill_total_price'),
('hosting', '0036_auto_20170506_2312'),
]
operations = [
]

View file

@ -1,6 +1,7 @@
import os import os
import oca import socket
import oca
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -15,6 +16,7 @@ from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
from .managers import VMPlansManager from .managers import VMPlansManager
from oca.exceptions import OpenNebulaException
from oca.pool import WrongNameError from oca.pool import WrongNameError
import logging import logging
@ -313,6 +315,47 @@ class ManageVM(models.Model):
class Meta: class Meta:
managed = False managed = False
class HostingBill(AssignPermissionsMixin, models.Model):
customer = models.ForeignKey(StripeCustomer)
billing_address = models.ForeignKey(BillingAddress)
total_price = models.FloatField(default=0.0)
permissions = ('view_hostingbill',)
class Meta:
permissions = (
('view_hostingbill', 'View Hosting Bill'),
)
def __str__(self):
return "%s" % (self.customer.user.email)
@classmethod
def create(cls, customer=None, billing_address=None):
instance = cls.objects.create(customer=customer, billing_address=billing_address)
return instance
def get_vms(self):
email = self.customer.user.email
# Get opennebula client
opennebula_client = OpenNebulaManager(create_user=False)
# Get vm pool
vm_pool = opennebula_client.get_vms(email)
# Reset total price
self.total_price = 0
vms = []
# Add vm in vm_pool to context
for vm in vm_pool:
vm_data = OpenNebulaManager.parse_vm(vm)
self.total_price += vm_data['price']
vms.append(vm_data)
self.save()
return vms
def get_user_opennebula_password(): def get_user_opennebula_password():
''' '''

View file

@ -0,0 +1,91 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% block content %}
<div class="container">
<div class="orders-container">
{# Adress bar #}
<div class="row">
<div class="invoice-title">
<h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-6">
<address>
{{bill.customer.user.name}}<br>
{{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
{{bill.billing_address.city}}, {{bill.billing_address.country}}.
</address>
</div>
<div class="col-sm-6 text-right">
<address>
{% trans "ungleich GmbH" %}<br>
{% trans "buchhaltung@ungleich.ch" %}<br>
{% trans "Hauptstrasse 14"%}<br>
{% trans "CH-8775 Luchsingen"%}<br>
{% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
</address>
</div>
</div>
<hr>
<table class="table table-bordered">
{# Bill header #}
<thead>
<tr>
<th>Name</th>
<th>Cores</th>
<th>Memory</th>
<th>Disk Size</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{# Bill items#}
{% for vm in vms %}
<tr>
<td>{{ vm.name }}</td>
<td><span class="pull-right">{{ vm.cores }}</span></td>
<td><span class="pull-right">{{ vm.memory|floatformat }} GiB </span></td>
<td><span class="pull-right">{{ vm.disk_size|floatformat }} GiB </span></td>
<td><span class="pull-right">{{ vm.price|floatformat }} CHF</span></td>
</tr>
{% endfor %}
{# Bill total#}
<tr>
<td colspan=4> {% trans "Total:" %} </td>
<td> <span class="pull-right">{{ bill.total_price|floatformat}} CHF </span></td>
</tr>
</tbody>
</table>
<hr>
{# Bill Footer #}
<div class="row">
{% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
{% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
{% trans "Kontoverbindung:" %}
<div class="row">
<div class="col-sm-6">
{% trans "IBAN:" %}
</div>
<div class="col-sm-6">
{% trans "BIC:" %}
</div>
</div>
<div class="row">
<div class="col-sm-6">
{% trans "CH02 ............" %}
</div>
<div class="col-sm-6">
{% trans "POFICHBEXXX" %}
</div>
</div>
</div>
<div>
</div>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% block content %}
<div class="container">
<div class="container orders-container">
<h1>Error</h1>
<p> Could not get HostingBill object for client. </p>
<p> Please create a HostingBill object via the admin page </p>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,60 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %}
{% load i18n %}
{% block content %}
<div>
<div class="container orders-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover">
<h3>{% trans "Customers"%}</h3>
<br/>
<thead>
<tr>
<th>{% trans "Name"%}</th>
<th>{% trans "Email"%}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.user.name}}</td>
<td>{{ user.user.email}}</td>
<td>
<button type="button" class="btn btn-default"><a
href="{% url 'hosting:bills' user.id %}">{% trans "View Bill"%}</a>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">{% trans "previous"%}</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">{% trans "next"%}</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -5,7 +5,7 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\ OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \ VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView,\ MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView,\
CreateVirtualMachinesView CreateVirtualMachinesView, HostingBillListView, HostingBillDetailView
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
@ -16,6 +16,8 @@ urlpatterns = [
url(r'payment/?$', PaymentVMView.as_view(), name='payment'), url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'), url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(), name='bills'),
url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'), url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'),
url(r'create-virtual-machine/?$', CreateVirtualMachinesView.as_view(), name='create-virtual-machine'), url(r'create-virtual-machine/?$', CreateVirtualMachinesView.as_view(), name='create-virtual-machine'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'), url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),

View file

@ -8,6 +8,7 @@ from django.views.generic import View, CreateView, FormView, ListView, DetailVie
DeleteView, TemplateView, UpdateView DeleteView, TemplateView, UpdateView
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib import messages
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect from django.shortcuts import redirect
@ -24,11 +25,14 @@ from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm from utils.forms import BillingAddressForm, PasswordResetRequestForm
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, UserHostingKey from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, HostingBill, UserHostingKey
from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from .opennebula_functions import HostingManageVMAdmin, OpenNebulaManager from .opennebula_functions import HostingManageVMAdmin, OpenNebulaManager
from oca.exceptions import OpenNebulaException
from oca.pool import WrongNameError
class DjangoHostingView(ProcessVMSelectionMixin, View): class DjangoHostingView(ProcessVMSelectionMixin, View):
template_name = "hosting/django.html" template_name = "hosting/django.html"
@ -317,6 +321,8 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# Create a Hosting Order # Create a Hosting Order
order = HostingOrder.create(vm_plan=plan, customer=customer, order = HostingOrder.create(vm_plan=plan, customer=customer,
billing_address=billing_address) billing_address=billing_address)
# Create a Hosting Bill
bill = HostingBill.create(customer=customer, billing_address=billing_address)
# Make stripe charge to a customer # Make stripe charge to a customer
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
@ -378,7 +384,6 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
permission_required = ['view_hostingorder'] permission_required = ['view_hostingorder']
model = HostingOrder model = HostingOrder
class OrdersHostingListView(LoginRequiredMixin, ListView): class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html" template_name = "hosting/orders.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
@ -532,3 +537,37 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
email.send() email.send()
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
class HostingBillListView(LoginRequiredMixin, ListView):
template_name = "hosting/bills.html"
login_url = reverse_lazy('hosting:login')
context_object_name = "users"
model = StripeCustomer
paginate_by = 10
ordering = '-id'
class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView):
template_name = "hosting/bill_detail.html"
login_url = reverse_lazy('hosting:login')
permission_required = ['view_hostingview']
context_object_name = "bill"
model = HostingBill
def get_object(self, queryset=None):
#Get HostingBill for primary key (Select from customer users)
pk = self.kwargs['pk']
object = HostingBill.objects.filter(customer__id=pk).first()
if object is None:
self.template_name = 'hosting/bill_error.html'
return object
def get_context_data(self, **kwargs):
# Get context
context = super(DetailView, self).get_context_data(**kwargs)
# Get vms
try:
context['vms'] = self.get_object().get_vms()
except:
pass
return context