commit
7cd2f1884d
12 changed files with 371 additions and 5 deletions
|
@ -5,7 +5,8 @@ from django.core.urlresolvers import reverse
|
|||
from utils.mailer import BaseEmail
|
||||
|
||||
from .forms import HostingOrderAdminForm
|
||||
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, ManageVM
|
||||
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, \
|
||||
ManageVM, HostingBill
|
||||
from .opennebula_functions import HostingManageVMAdmin
|
||||
|
||||
|
||||
|
@ -98,3 +99,4 @@ admin.site.register(HostingOrder, HostingOrderAdmin)
|
|||
admin.site.register(VirtualMachineType)
|
||||
admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin)
|
||||
admin.site.register(ManageVM, HostingManageVMAdmin)
|
||||
admin.site.register(HostingBill)
|
||||
|
|
24
hosting/migrations/0028_managevms.py
Normal file
24
hosting/migrations/0028_managevms.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
24
hosting/migrations/0029_managevm.py
Normal file
24
hosting/migrations/0029_managevm.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
31
hosting/migrations/0030_hostingbill.py
Normal file
31
hosting/migrations/0030_hostingbill.py
Normal 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),
|
||||
),
|
||||
]
|
20
hosting/migrations/0031_hostingbill_total_price.py
Normal file
20
hosting/migrations/0031_hostingbill_total_price.py
Normal 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),
|
||||
),
|
||||
]
|
16
hosting/migrations/0037_merge.py
Normal file
16
hosting/migrations/0037_merge.py
Normal 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 = [
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import oca
|
||||
import socket
|
||||
|
||||
import oca
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -15,6 +16,7 @@ from membership.models import StripeCustomer, CustomUser
|
|||
from utils.models import BillingAddress
|
||||
from utils.mixins import AssignPermissionsMixin
|
||||
from .managers import VMPlansManager
|
||||
from oca.exceptions import OpenNebulaException
|
||||
from oca.pool import WrongNameError
|
||||
|
||||
import logging
|
||||
|
@ -313,6 +315,47 @@ class ManageVM(models.Model):
|
|||
|
||||
class Meta:
|
||||
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():
|
||||
'''
|
||||
|
|
91
hosting/templates/hosting/bill_detail.html
Normal file
91
hosting/templates/hosting/bill_detail.html
Normal 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 %}
|
||||
|
14
hosting/templates/hosting/bill_error.html
Normal file
14
hosting/templates/hosting/bill_error.html
Normal 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 %}
|
||||
|
60
hosting/templates/hosting/bills.html
Normal file
60
hosting/templates/hosting/bills.html
Normal 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 %}
|
|
@ -5,7 +5,7 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
|
|||
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
|
||||
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
|
||||
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView,\
|
||||
CreateVirtualMachinesView
|
||||
CreateVirtualMachinesView, HostingBillListView, HostingBillDetailView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'index/?$', IndexView.as_view(), name='index'),
|
||||
|
@ -16,6 +16,8 @@ urlpatterns = [
|
|||
url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
|
||||
url(r'orders/?$', OrdersHostingListView.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'create-virtual-machine/?$', CreateVirtualMachinesView.as_view(), name='create-virtual-machine'),
|
||||
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.views.generic import View, CreateView, FormView, ListView, DetailVie
|
|||
DeleteView, TemplateView, UpdateView
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib import messages
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
|
||||
|
@ -24,11 +25,14 @@ from utils.stripe_utils import StripeUtils
|
|||
from utils.forms import BillingAddressForm, PasswordResetRequestForm
|
||||
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
|
||||
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 .mixins import ProcessVMSelectionMixin
|
||||
from .opennebula_functions import HostingManageVMAdmin, OpenNebulaManager
|
||||
|
||||
from oca.exceptions import OpenNebulaException
|
||||
from oca.pool import WrongNameError
|
||||
|
||||
|
||||
class DjangoHostingView(ProcessVMSelectionMixin, View):
|
||||
template_name = "hosting/django.html"
|
||||
|
@ -317,6 +321,8 @@ class PaymentVMView(LoginRequiredMixin, FormView):
|
|||
# Create a Hosting Order
|
||||
order = HostingOrder.create(vm_plan=plan, customer=customer,
|
||||
billing_address=billing_address)
|
||||
# Create a Hosting Bill
|
||||
bill = HostingBill.create(customer=customer, billing_address=billing_address)
|
||||
|
||||
# Make stripe charge to a customer
|
||||
stripe_utils = StripeUtils()
|
||||
|
@ -378,7 +384,6 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
|
|||
permission_required = ['view_hostingorder']
|
||||
model = HostingOrder
|
||||
|
||||
|
||||
class OrdersHostingListView(LoginRequiredMixin, ListView):
|
||||
template_name = "hosting/orders.html"
|
||||
login_url = reverse_lazy('hosting:login')
|
||||
|
@ -532,3 +537,37 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, View):
|
|||
email.send()
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue