diff --git a/hosting/admin.py b/hosting/admin.py index b4f1ba24..ee4e7415 100644 --- a/hosting/admin.py +++ b/hosting/admin.py @@ -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) diff --git a/hosting/migrations/0028_managevms.py b/hosting/migrations/0028_managevms.py new file mode 100644 index 00000000..b71480f2 --- /dev/null +++ b/hosting/migrations/0028_managevms.py @@ -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, + }, + ), + ] diff --git a/hosting/migrations/0029_managevm.py b/hosting/migrations/0029_managevm.py new file mode 100644 index 00000000..946e4264 --- /dev/null +++ b/hosting/migrations/0029_managevm.py @@ -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, + }, + ), + ] diff --git a/hosting/migrations/0030_hostingbill.py b/hosting/migrations/0030_hostingbill.py new file mode 100644 index 00000000..edb01aed --- /dev/null +++ b/hosting/migrations/0030_hostingbill.py @@ -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), + ), + ] diff --git a/hosting/migrations/0031_hostingbill_total_price.py b/hosting/migrations/0031_hostingbill_total_price.py new file mode 100644 index 00000000..0a15c1f9 --- /dev/null +++ b/hosting/migrations/0031_hostingbill_total_price.py @@ -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), + ), + ] diff --git a/hosting/migrations/0037_merge.py b/hosting/migrations/0037_merge.py new file mode 100644 index 00000000..091a16c5 --- /dev/null +++ b/hosting/migrations/0037_merge.py @@ -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 = [ + ] diff --git a/hosting/models.py b/hosting/models.py index bceaaacf..b59b7f5a 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -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(): ''' diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html new file mode 100644 index 00000000..2d7f04b4 --- /dev/null +++ b/hosting/templates/hosting/bill_detail.html @@ -0,0 +1,91 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 %} +{% load i18n %} +{% block content %} + +
+
+ {# Adress bar #} +
+
+

{% trans "Invoice"%}

{% trans "Order #"%} {{bill.id}}

+
+
+
+
+
+
+ {{bill.customer.user.name}}
+ {{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}
+ {{bill.billing_address.city}}, {{bill.billing_address.country}}. +
+
+
+
+ {% trans "ungleich GmbH" %}
+ {% trans "buchhaltung@ungleich.ch" %}
+ {% trans "Hauptstrasse 14"%}
+ {% trans "CH-8775 Luchsingen"%}
+ {% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}
+ +
+
+
+
+ + {# Bill header #} + + + + + + + + + + + {# Bill items#} + {% for vm in vms %} + + + + + + + + + {% endfor %} + {# Bill total#} + + + + + +
NameCoresMemoryDisk SizePrice
{{ vm.name }}{{ vm.cores }}{{ vm.memory|floatformat }} GiB {{ vm.disk_size|floatformat }} GiB {{ vm.price|floatformat }} CHF
{% trans "Total:" %} {{ bill.total_price|floatformat}} CHF
+
+ {# Bill Footer #} +
+ {% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %} + {% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %} + {% trans "Kontoverbindung:" %} +
+
+ {% trans "IBAN:" %} +
+
+ {% trans "BIC:" %} +
+
+
+
+ {% trans "CH02 ............" %} +
+
+ {% trans "POFICHBEXXX" %} +
+
+
+
+
+{% endblock %} + diff --git a/hosting/templates/hosting/bill_error.html b/hosting/templates/hosting/bill_error.html new file mode 100644 index 00000000..5374ecb5 --- /dev/null +++ b/hosting/templates/hosting/bill_error.html @@ -0,0 +1,14 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 %} +{% load i18n %} +{% block content %} + +
+
+

Error

+

Could not get HostingBill object for client.

+

Please create a HostingBill object via the admin page

+
+
+{% endblock %} + diff --git a/hosting/templates/hosting/bills.html b/hosting/templates/hosting/bills.html new file mode 100644 index 00000000..1e789e12 --- /dev/null +++ b/hosting/templates/hosting/bills.html @@ -0,0 +1,60 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 %} +{% load i18n %} + +{% block content %} + +
+
+
+
+ +

{% trans "Customers"%}

+
+ + + + + + + + + {% for user in users %} + + + + + + {% endfor %} + + +
{% trans "Name"%}{% trans "Email"%}
{{ user.user.name}}{{ user.user.email}} + +
+ + {% if is_paginated %} + + {% endif %} + +
+ +
+
+ +
+ +{% endblock %} diff --git a/hosting/urls.py b/hosting/urls.py index 08473bb8..abfb8d5f 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -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\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), + url(r'bills/?$', HostingBillListView.as_view(), name='bills'), + url(r'bills/(?P\d+)/?$', HostingBillDetailView.as_view(), name='bills'), url(r'cancel_order/(?P\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'), diff --git a/hosting/views.py b/hosting/views.py index 8989732f..cf1cf879 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -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