diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.mo b/datacenterlight/locale/de/LC_MESSAGES/django.mo index b918fa42..10bfb438 100644 Binary files a/datacenterlight/locale/de/LC_MESSAGES/django.mo and b/datacenterlight/locale/de/LC_MESSAGES/django.mo differ diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 9dba2ea2..b8a8d37c 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-01-22 14:06-0500\n" +"POT-Creation-Date: 2017-05-20 11:12-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -51,28 +51,28 @@ msgstr "" msgid "Thank you!" msgstr "Vielen Dank!" -#: templates/datacenterlight/index.html:60 +#: templates/datacenterlight/index.html:62 msgid "What is it" msgstr "Was ist es?" -#: templates/datacenterlight/index.html:63 -#: templates/datacenterlight/index.html:165 -#: templates/datacenterlight/index.html:351 +#: templates/datacenterlight/index.html:65 +#: templates/datacenterlight/index.html:171 +#: templates/datacenterlight/index.html:362 msgid "Scale out" msgstr "Skalierung" -#: templates/datacenterlight/index.html:66 -#: templates/datacenterlight/index.html:188 -#: templates/datacenterlight/index.html:354 +#: templates/datacenterlight/index.html:68 +#: templates/datacenterlight/index.html:197 +#: templates/datacenterlight/index.html:365 msgid "Reliable and light" msgstr "Zuverlässig und leicht" -#: templates/datacenterlight/index.html:69 +#: templates/datacenterlight/index.html:71 msgid "Buy VM" msgstr "VM Kaufen" -#: templates/datacenterlight/index.html:72 -#: templates/datacenterlight/index.html:361 +#: templates/datacenterlight/index.html:74 +#: templates/datacenterlight/index.html:372 msgid "Contact" msgstr "Kontakt" @@ -88,27 +88,30 @@ msgstr "Was ist es?" msgid "I want it!" msgstr "Das will ich haben!" -#: templates/datacenterlight/index.html:139 -msgid "How it works:" -msgstr "Warum können wir diese Leistung so günstig anbieten:" +#: templates/datacenterlight/index.html:142 +#: templates/datacenterlight/index.html:359 +msgid "How it works" +msgstr "Wie es funktioniert" -#: templates/datacenterlight/index.html:141 +#: templates/datacenterlight/index.html:147 msgid "Reuse existing factory halls intead of building an expensive building." msgstr "" -"Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der Errichtung eines neuen Gebäudes" +"Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der " +"Errichtung eines neuen Gebäudes" -#: templates/datacenterlight/index.html:144 +#: templates/datacenterlight/index.html:150 msgid "Being creative, using modern and alternative design for a datacenter." msgstr "" -"Kreativität: Verwendung eines modernen und alternativen Designs für unser Datencenter" +"Kreativität: Verwendung eines modernen und alternativen Designs für unser " +"Datencenter" -#: templates/datacenterlight/index.html:146 +#: templates/datacenterlight/index.html:152 msgid "Being open: Using FOSS exclusively, we can save money for licenses." msgstr "" "Offene Verfahrensweise: Die Benutzung eines eigenen Frameworks, FOSS, " "erspart Lizenzgebühren" -#: templates/datacenterlight/index.html:166 +#: templates/datacenterlight/index.html:174 msgid "" "We don't use special hardware. We use commodity hardware: we buy computers " "that you buy. Just many more and put them in a cozy home for computers " @@ -118,90 +121,88 @@ msgstr "" "erschwingliche Systeme. Bei grösserer Auslastung werden mehr Standard " "komponenten hinzugekauft und skalieren so das Datencenter." -#: templates/datacenterlight/index.html:189 +#: templates/datacenterlight/index.html:200 msgid "" "Our VMs are located in Switzerland, with reliable power supply and fast " "internet connection. Our VM costs less thanks to our featherlight " "infrastructure." msgstr "" "Unser Datacenter befindet sich in der Schweiz und ist mit zuverlässiger " -"Energieversorgung sowie schneller Internetverbindung ausgestattet. " -"Unser Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig." +"Energieversorgung sowie schneller Internetverbindung ausgestattet. Unser " +"Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig." -#: templates/datacenterlight/index.html:211 +#: templates/datacenterlight/index.html:218 msgid "We are cutting down the costs significantly!" msgstr "Wir sorgen dafür, dass die Kosten für Sie signifikant abnehmen" -#: templates/datacenterlight/index.html:212 +#: templates/datacenterlight/index.html:219 msgid "Affordable VM hosting based in Switzerland" msgstr "Bezahlbares VM Hosting in der Schweiz" -#: templates/datacenterlight/index.html:228 -msgid "VM hosting" -msgstr "" - -#: templates/datacenterlight/index.html:229 -msgid "Based in Switzerland" -msgstr "Standort des Datacenters ist in der Schweiz" - -#: templates/datacenterlight/index.html:232 -msgid "15 GiB storage(SSD)" -msgstr "" - -#: templates/datacenterlight/index.html:234 -msgid "Buy Now!" -msgstr "Kaufe jetzt!" - -#: templates/datacenterlight/index.html:234 +#: templates/datacenterlight/index.html:220 msgid "More Info" msgstr "Weitere Informationen" -#: templates/datacenterlight/index.html:256 +#: templates/datacenterlight/index.html:226 +msgid "VM hosting" +msgstr "" + +#: templates/datacenterlight/index.html:233 +msgid "Based in Switzerland" +msgstr "Standort des Datacenters ist in der Schweiz" + +#: templates/datacenterlight/index.html:242 +msgid "15 GiB storage(SSD)" +msgstr "" + +#: templates/datacenterlight/index.html:245 +msgid "Buy Now!" +msgstr "Kaufe jetzt!" + +#: templates/datacenterlight/index.html:259 msgid "I want to try!" msgstr "Das möchte ich haben" -#: templates/datacenterlight/index.html:269 -msgid "Email address" -msgstr "E-Mail Adresse" - -#: templates/datacenterlight/index.html:272 +#: templates/datacenterlight/index.html:281 msgid "Request Beta Access" msgstr "Beantrage Beta-Zugang" -#: templates/datacenterlight/index.html:281 +#: templates/datacenterlight/index.html:289 #, fuzzy #| msgid "Request Beta Access" msgid "Request Sent" msgstr "Anfrage verschickt" -#: templates/datacenterlight/index.html:284 +#: templates/datacenterlight/index.html:292 msgid "Thank you, we will contact you as soon as possible" msgstr "Vielen Dank, wir werden Sie sobald als möglich kontaktieren." -#: templates/datacenterlight/index.html:314 -msgid "Questions?" -msgstr "Fragen?" - -#: templates/datacenterlight/index.html:315 -msgid "Contact us!" -msgstr "Kontaktiere uns!" - -#: templates/datacenterlight/index.html:319 +#: templates/datacenterlight/index.html:320 msgid "Switzerland " msgstr "Schweiz" -#: templates/datacenterlight/index.html:344 +#: templates/datacenterlight/index.html:337 +msgid "Questions?" +msgstr "Fragen?" + +#: templates/datacenterlight/index.html:337 +msgid "Contact us!" +msgstr "Kontaktiere uns!" + +#: templates/datacenterlight/index.html:355 msgid "Home" msgstr "Home" -#: templates/datacenterlight/index.html:348 -msgid "How it works" -msgstr "Wie es funktioniert" - -#: templates/datacenterlight/index.html:357 +#: templates/datacenterlight/index.html:368 msgid "Pricing" msgstr "Preise" +#~ msgid "How it works:" +#~ msgstr "Warum können wir diese Leistung so günstig anbieten:" + +#~ msgid "Email address" +#~ msgstr "E-Mail Adresse" + #~ msgid "Our promise" #~ msgstr "Unser Versprechen" diff --git a/datacenterlight/static/datacenterlight/css/landing-page.css b/datacenterlight/static/datacenterlight/css/landing-page.css index e881f880..07f03f97 100755 --- a/datacenterlight/static/datacenterlight/css/landing-page.css +++ b/datacenterlight/static/datacenterlight/css/landing-page.css @@ -461,7 +461,6 @@ h6 { padding-bottom: 25px; position: relative; text-align: right; - text-transform: capitalize; } .contact-section .title h2::before{ content: ""; diff --git a/datacenterlight/templates/datacenterlight/index.html b/datacenterlight/templates/datacenterlight/index.html index 18e2ac1c..953ce2c6 100755 --- a/datacenterlight/templates/datacenterlight/index.html +++ b/datacenterlight/templates/datacenterlight/index.html @@ -219,6 +219,7 @@

{% trans "Affordable VM hosting based in Switzerland" %}

{% trans "More Info" %} +
@@ -334,7 +335,7 @@
-

{% trans "Questions?" %} {% trans "Contact Us!" %}

+

{% trans "Questions?" %} {% trans "Contact us!" %}

diff --git a/datacenterlight/views.py b/datacenterlight/views.py index afc16f03..ce50bd5a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -6,6 +6,8 @@ from django.contrib import messages from django.core.urlresolvers import reverse_lazy, reverse from utils.mailer import BaseEmail +from opennebula_api.models import OpenNebulaManager +from opennebula_api.serializers import VirtualMachineTemplateSerializer class LandingProgramView(TemplateView): template_name = "datacenterlight/landing.html" @@ -27,6 +29,10 @@ class BetaProgramView(CreateView): def get_context_data(self, **kwargs): vms = BetaAccessVMType.objects.all() context = super(BetaProgramView, self).get_context_data(**kwargs) + + # templates = OpenNebulaManager().get_templates() + # data = VirtualMachineTemplateSerializer(templates, many=True).data + context.update({ 'base_url': "{0}://{1}".format(self.request.scheme, self.request.get_host()), 'vms': vms diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 617bbe78..d9ccd7ac 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -111,6 +111,8 @@ INSTALLED_APPS = ( 'nosystemd', 'datacenterlight', 'alplora', + 'rest_framework', + 'opennebula_api' ) MIDDLEWARE_CLASSES = ( @@ -473,4 +475,31 @@ else: ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch' -GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance' \ No newline at end of file +GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance' + + +############################################# +# configurations for opennebula-integration # +############################################# + +# The oneadmin user name of the OpenNebula infrastructure +OPENNEBULA_USERNAME = env('OPENNEBULA_USERNAME') + +# The oneadmin password of the OpenNebula infrastructure +# The default credentials of the Sandbox OpenNebula VM is +# oneadmin:opennebula +OPENNEBULA_PASSWORD = env('OPENNEBULA_PASSWORD') + +# The protocol is generally http or https +OPENNEBULA_PROTOCOL = env('OPENNEBULA_PROTOCOL') + +# The ip address or the domain name of the opennebula infrastructure +OPENNEBULA_DOMAIN = env('OPENNEBULA_DOMAIN') + +# The port to connect in order to send an xmlrpc request. The default +# port is 2633 +OPENNEBULA_PORT = env('OPENNEBULA_PORT') + +# The endpoint to which the XML RPC request needs to be sent to. The +# default value is /RPC2 +OPENNEBULA_ENDPOINT = env('OPENNEBULA_ENDPOINT') diff --git a/dynamicweb/settings/prod.py b/dynamicweb/settings/prod.py index 3d268ed4..bb6577b5 100644 --- a/dynamicweb/settings/prod.py +++ b/dynamicweb/settings/prod.py @@ -3,7 +3,7 @@ from .base import * ADMINS = ( ('Nico Schottelius', 'nico.schottelius@ungleich.ch'), ('Raul Ascencio', 'raul.ascencio@yandex.com'), - ('Tomislav Rupcic','tmslav@gmail.com'), + ('Web team', 'web-team@ungleich.ch') ) # ('Sanghee Kim', 'sanghee.kim@ungleich.ch'), diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 5e6b99ee..0b1a0844 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -8,10 +8,14 @@ from django.conf import settings from hosting.views import RailsHostingView, DjangoHostingView, NodeJSHostingView from membership import urls as membership_urls from ungleich_page.views import LandingView +from django.views.generic import RedirectView +from django.core.urlresolvers import reverse_lazy import debug_toolbar urlpatterns = [ url(r'^index.html$', LandingView.as_view()), url(r'^hosting/', include('hosting.urls', namespace="hosting")), + url(r'^open_api/', include('opennebula_api.urls', + namespace='opennebula_api')), url(r'^railshosting/', RailsHostingView.as_view(), name="rails.hosting"), url(r'^nodehosting/', NodeJSHostingView.as_view(), name="node.hosting"), url(r'^djangohosting/', DjangoHostingView.as_view(), name="django.hosting"), @@ -26,6 +30,7 @@ urlpatterns += i18n_patterns('', url(r'^/?$', LandingView.as_view()), url(r'^admin/', include(admin.site.urls)), url(r'^datacenterlight', include('datacenterlight.urls', namespace="datacenterlight")), + url(r'^hosting/', RedirectView.as_view(url=reverse_lazy('hosting:login')), name='redirect_hosting_login'), url(r'^alplora', include('alplora.urls', namespace="alplora")), url(r'^membership/', include(membership_urls)), url(r'^digitalglarus/', include('digitalglarus.urls', diff --git a/hosting/README-opennebula-integration.md b/hosting/README-opennebula-integration.md new file mode 100644 index 00000000..7f6251a9 --- /dev/null +++ b/hosting/README-opennebula-integration.md @@ -0,0 +1,41 @@ +Here are the steps to follow for running opennebula-integration correctly. + +1. Install [python-oca](https://github.com/python-oca/python-oca) +This is the library that allows sending XMLRPC commands to OpenNebula. Unfortunately, the latest version of oca available in Python package index is not compatible with python 3.5. Hence, one would need to download the latest version from the above github link and install it from there. +Assuming virtualenv is located at ~/python/env + +``` +~/python/env/bin/python setup.py build +sudo ~/python/env/bin/python setup.py install +``` + +2. Setup opennebula parameters in the `.env` file. + +``` +############################################# +# configurations for opennebula-integration # +############################################# + +# The oneadmin user name of the OpenNebula infrastructure +OPENNEBULA_USERNAME='oneadmin' + +# The oneadmin password of the OpenNebula infrastructure +# The default credentials of the Sandbox OpenNebula VM is +# oneadmin:opennebula +OPENNEBULA_PASSWORD='opennebula' + +# The protocol is generally http or https +OPENNEBULA_PROTOCOL='http' + +# The ip address or the domain name of the opennebula infrastructure +OPENNEBULA_DOMAIN='192.168.182.124' + +# The port to connect in order to send an xmlrpc request. The default +# port is 2633 +OPENNEBULA_PORT='2633' + +# The endpoint to which the XML RPC request needs to be sent to. The +# default value is /RPC2 +OPENNEBULA_ENDPOINT='/RPC2' +``` + diff --git a/hosting/admin.py b/hosting/admin.py index c4bff6e0..c38fa8d0 100644 --- a/hosting/admin.py +++ b/hosting/admin.py @@ -4,95 +4,9 @@ from django.core.urlresolvers import reverse from utils.mailer import BaseEmail -from .forms import HostingOrderAdminForm -from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder +from .models import HostingOrder, HostingBill, HostingPlan -class HostingOrderAdmin(admin.ModelAdmin): - # fields = ('slug', 'imdb_link', 'start', 'finish', 'added_by') - list_display = ('id', 'created_at', 'plan', 'user') - search_fields = ['vm_plan__id', 'customer__user__email'] - - def save_model(self, request, obj, form, change): - if not change: - customer = form.cleaned_data.get('customer') - - # Get and set billing address from the lastest charged order - last_order = HostingOrder.objects.filter(customer=customer).latest('id') - billing_address = last_order.billing_address - obj.billing_address = billing_address - - charge = form.cleaned_data.get('charge') - # Associate an order with a stripe payment - obj.set_stripe_charge(charge) - - # If the Stripe payment was successed, set order status approved - obj.set_approved() - - # Assigning permissions - obj.assign_permissions(customer.user) - - context = { - 'order': obj, - 'vm': obj.vm_plan, - 'base_url': "{0}://{1}".format(request.scheme, request.get_host()) - } - email_data = { - 'subject': 'Your VM plan has been charged', - 'to': obj.customer.user.email, - 'context': context, - 'template_name': 'vm_charged', - 'template_path': 'hosting/emails/' - } - email = BaseEmail(**email_data) - email.send() - - obj.save() - return obj - - def get_form(self, request, obj=None, **kwargs): - if obj is None: - kwargs['form'] = HostingOrderAdminForm - return super(HostingOrderAdmin, self).get_form(request, obj, **kwargs) - - def user(self, obj): - email = obj.customer.user.email - user_url = reverse("admin:membership_customuser_change", args=[obj.customer.user.id]) - return format_html("{email}", url=user_url, email=email) - - def plan(self, obj): - vm_name = obj.vm_plan.name - vm_url = reverse("admin:hosting_virtualmachineplan_change", args=[obj.vm_plan.id]) - return format_html("{vm_name}", url=vm_url, vm_name=vm_name) - - plan.short_description = "Virtual Machine Plan" - - -class VirtualMachinePlanAdmin(admin.ModelAdmin): - list_display = ('name', 'id', 'email') - - def email(self, obj): - return obj.hosting_orders.latest('id').customer.user.email - - def save_model(self, request, obj, form, change): - email = self.email(obj) - if 'status' in form.changed_data: - context = { - 'vm': obj, - 'base_url': "{0}://{1}".format(request.scheme, request.get_host()) - } - email_data = { - 'subject': 'Your VM has been activated', - 'to': email, - 'context': context, - 'template_name': 'vm_status_changed', - 'template_path': 'hosting/emails/' - } - email = BaseEmail(**email_data) - email.send() - obj.save() - - -admin.site.register(HostingOrder, HostingOrderAdmin) -admin.site.register(VirtualMachineType) -admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin) +admin.site.register(HostingOrder) +admin.site.register(HostingBill) +admin.site.register(HostingPlan) diff --git a/hosting/forms.py b/hosting/forms.py index 2a4d67e3..c94c4822 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -1,42 +1,13 @@ +import random +import string from django import forms from membership.models import CustomUser from django.contrib.auth import authenticate + from utils.stripe_utils import StripeUtils -from .models import HostingOrder, VirtualMachinePlan - - -class HostingOrderAdminForm(forms.ModelForm): - - class Meta: - model = HostingOrder - fields = ['vm_plan', 'customer'] - - def clean(self): - customer = self.cleaned_data.get('customer') - vm_plan = self.cleaned_data.get('vm_plan') - - if vm_plan.status == VirtualMachinePlan.CANCELED_STATUS: - raise forms.ValidationError("""You can't make a charge over - a canceled virtual machine plan""") - - if not customer: - raise forms.ValidationError("""You need select a costumer""") - - # Make a charge to the customer - stripe_utils = StripeUtils() - charge_response = stripe_utils.make_charge(customer=customer.stripe_id, - amount=vm_plan.price) - charge = charge_response.get('response_object') - if not charge: - raise forms.ValidationError(charge_response.get('error')) - - self.cleaned_data.update({ - 'charge': charge - }) - return self.cleaned_data - +from .models import HostingOrder, UserHostingKey class HostingUserLoginForm(forms.Form): @@ -83,3 +54,40 @@ class HostingUserSignupForm(forms.ModelForm): if not confirm_password == password: raise forms.ValidationError("Passwords don't match") return confirm_password + + +class UserHostingKeyForm(forms.ModelForm): + private_key = forms.CharField(widget=forms.PasswordInput(), required=False) + public_key = forms.CharField(widget=forms.PasswordInput(), required=False) + user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(), required=False) + name = forms.CharField(required=False) + + def __init__(self, *args, **kwargs): + self.request = kwargs.pop("request") + super(UserHostingKeyForm, self).__init__(*args, **kwargs) + # self.initial['user'].initial = self.request.user.id + # print(self.fields) + + def clean_name(self): + return "dcl-priv-key-%s" % ( + ''.join(random.choice(string.ascii_lowercase) for i in range(7)) + ) + + def clean_user(self): + return self.request.user + + def clean(self): + cleaned_data = self.cleaned_data + + if not cleaned_data.get('public_key'): + private_key, public_key = UserHostingKey.generate_keys() + cleaned_data.update({ + 'private_key': private_key, + 'public_key': public_key + }) + + return cleaned_data + + class Meta: + model = UserHostingKey + fields = ['user', 'public_key', 'name'] diff --git a/hosting/management/commands/create_vm_types.py b/hosting/management/commands/create_vm_types.py index 6be49f19..f92cc3a1 100644 --- a/hosting/management/commands/create_vm_types.py +++ b/hosting/management/commands/create_vm_types.py @@ -7,51 +7,100 @@ class Command(BaseCommand): def get_data(self): + return [ + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 1, + 'memory': 2, + 'disk_size': 10 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 1, + 'memory': 2, + 'disk_size': 100 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 2, + 'memory': 4, + 'disk_size': 20 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 4, + 'memory': 8, + 'disk_size': 40 + }, + { + 'base_price': 10, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, + 'cores': 16, + 'memory': 8, + 'disk_size': 40 + }, + ] + + hetzner = { 'base_price': 10, - 'core_price': 10, - 'memory_price': 5, - 'disk_size_price': 1, + 'core_price': 5, + 'memory_price': 2, + 'disk_size_price': 0.6, 'description': 'VM auf einzelner HW, Raid1, kein HA', 'location': 'DE' } - return { - # 'hetzner_nug': { - # 'base_price': 5, - # 'memory_price': 2, - # 'core_price': 2, - # 'disk_size_price': 0.5, - # 'description': 'VM ohne Uptime Garantie' - # }, - 'hetzner': hetzner, - # 'hetzner_raid6': { - # 'base_price': hetzner['base_price']*1.2, - # 'core_price': hetzner['core_price']*1.2, - # 'memory_price': hetzner['memory_price']*1.2, - # 'disk_size_price': hetzner['disk_size_price']*1.2, - # 'description': 'VM auf einzelner HW, Raid1, kein HA' + # return { + # # 'hetzner_nug': { + # # 'base_price': 5, + # # 'memory_price': 2, + # # 'core_price': 2, + # # 'disk_size_price': 0.5, + # # 'description': 'VM ohne Uptime Garantie' + # # }, + # 'hetzner': hetzner, + # # 'hetzner_raid6': { + # # 'base_price': hetzner['base_price']*1.2, + # # 'core_price': hetzner['core_price']*1.2, + # # 'memory_price': hetzner['memory_price']*1.2, + # # 'disk_size_price': hetzner['disk_size_price']*1.2, + # # 'description': 'VM auf einzelner HW, Raid1, kein HA' - # }, - # 'hetzner_glusterfs': { - # 'base_price': hetzner['base_price']*1.4, - # 'core_price': hetzner['core_price']*1.4, - # 'memory_price': hetzner['memory_price']*1.4, - # 'disk_size_price': hetzner['disk_size_price']*1.4, - # 'description': 'VM auf einzelner HW, Raid1, kein HA' - # }, - 'bern': { - 'base_price': 12, - 'core_price': 25, - 'memory_price': 7, - 'disk_size_price': 0.70, - 'description': "VM in Bern, HA Setup ohne HA Garantie", - 'location': 'CH', - } - } + # # }, + # # 'hetzner_glusterfs': { + # # 'base_price': hetzner['base_price']*1.4, + # # 'core_price': hetzner['core_price']*1.4, + # # 'memory_price': hetzner['memory_price']*1.4, + # # 'disk_size_price': hetzner['disk_size_price']*1.4, + # # 'description': 'VM auf einzelner HW, Raid1, kein HA' + # # }, + # 'bern': { + # 'base_price': 12, + # 'core_price': 25, + # 'memory_price': 7, + # 'disk_size_price': 0.70, + # 'description': "VM in Bern, HA Setup ohne HA Garantie", + # 'location': 'CH', + # } + # } def handle(self, *args, **options): - data = self.get_data() - [VirtualMachineType.objects.create(hosting_company=key, **data[key]) - for key in data.keys()] + vm_data = self.get_data() + for vm in vm_data: + VirtualMachineType.objects.create(**vm) diff --git a/hosting/migrations/0028_managevm_userhostingkey.py b/hosting/migrations/0028_managevm_userhostingkey.py new file mode 100644 index 00000000..75bf591a --- /dev/null +++ b/hosting/migrations/0028_managevm_userhostingkey.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-04-29 18:28 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hosting', '0027_auto_20160711_0210'), + ] + + operations = [ + migrations.CreateModel( + name='ManageVM', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'managed': False, + }, + ), + migrations.CreateModel( + name='UserHostingKey', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('public_key', models.TextField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] 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/0029_userhostingkey_created_at.py b/hosting/migrations/0029_userhostingkey_created_at.py new file mode 100644 index 00000000..6ab968fd --- /dev/null +++ b/hosting/migrations/0029_userhostingkey_created_at.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-04-30 19:04 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0028_managevm_userhostingkey'), + ] + + operations = [ + migrations.AddField( + model_name='userhostingkey', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2017, 4, 30, 19, 4, 20, 780173, tzinfo=utc)), + preserve_default=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/0030_userhostingkey_name.py b/hosting/migrations/0030_userhostingkey_name.py new file mode 100644 index 00000000..7405d66f --- /dev/null +++ b/hosting/migrations/0030_userhostingkey_name.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-04-30 19:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0029_userhostingkey_created_at'), + ] + + operations = [ + migrations.AddField( + model_name='userhostingkey', + name='name', + field=models.CharField(default='', max_length=100), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0031_auto_20170503_0554.py b/hosting/migrations/0031_auto_20170503_0554.py new file mode 100644 index 00000000..acf9ae62 --- /dev/null +++ b/hosting/migrations/0031_auto_20170503_0554.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-03 05:54 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0030_userhostingkey_name'), + ] + + operations = [ + migrations.RemoveField( + model_name='virtualmachinetype', + name='hosting_company', + ), + migrations.RemoveField( + model_name='virtualmachinetype', + name='location', + ), + ] 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/0032_auto_20170504_0315.py b/hosting/migrations/0032_auto_20170504_0315.py new file mode 100644 index 00000000..c16b382a --- /dev/null +++ b/hosting/migrations/0032_auto_20170504_0315.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-04 03:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0031_auto_20170503_0554'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachinetype', + name='cores', + field=models.IntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name='virtualmachinetype', + name='disk_size', + field=models.IntegerField(default=0), + preserve_default=False, + ), + migrations.AddField( + model_name='virtualmachinetype', + name='memory', + field=models.IntegerField(default=0), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0033_virtualmachinetype_configuration.py b/hosting/migrations/0033_virtualmachinetype_configuration.py new file mode 100644 index 00000000..ecd6d5f4 --- /dev/null +++ b/hosting/migrations/0033_virtualmachinetype_configuration.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-04 03:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0032_auto_20170504_0315'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachinetype', + name='configuration', + field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], default='ubuntu', max_length=10), + ), + ] diff --git a/hosting/migrations/0034_auto_20170504_0331.py b/hosting/migrations/0034_auto_20170504_0331.py new file mode 100644 index 00000000..0dd66012 --- /dev/null +++ b/hosting/migrations/0034_auto_20170504_0331.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-04 03:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0033_virtualmachinetype_configuration'), + ] + + operations = [ + migrations.RemoveField( + model_name='virtualmachinetype', + name='configuration', + ), + migrations.AlterField( + model_name='virtualmachineplan', + name='configuration', + field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], max_length=20), + ), + migrations.AlterField( + model_name='virtualmachineplan', + name='vm_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachineType'), + ), + ] diff --git a/hosting/migrations/0035_virtualmachineplan_opennebula_id.py b/hosting/migrations/0035_virtualmachineplan_opennebula_id.py new file mode 100644 index 00000000..9b23875d --- /dev/null +++ b/hosting/migrations/0035_virtualmachineplan_opennebula_id.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-06 23:02 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0034_auto_20170504_0331'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachineplan', + name='opennebula_id', + field=models.IntegerField(default=0), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0036_auto_20170506_2312.py b/hosting/migrations/0036_auto_20170506_2312.py new file mode 100644 index 00000000..14449527 --- /dev/null +++ b/hosting/migrations/0036_auto_20170506_2312.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-06 23:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0035_virtualmachineplan_opennebula_id'), + ] + + operations = [ + migrations.AlterField( + model_name='virtualmachineplan', + name='opennebula_id', + field=models.IntegerField(null=True), + ), + ] 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/migrations/0038_auto_20170512_1006.py b/hosting/migrations/0038_auto_20170512_1006.py new file mode 100644 index 00000000..716faa38 --- /dev/null +++ b/hosting/migrations/0038_auto_20170512_1006.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-12 10:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0037_merge'), + ] + + operations = [ + migrations.RemoveField( + model_name='virtualmachineplan', + name='vm_type', + ), + migrations.RemoveField( + model_name='hostingorder', + name='vm_plan', + ), + migrations.AddField( + model_name='hostingorder', + name='vm_id', + field=models.IntegerField(default=0), + ), + migrations.DeleteModel( + name='VirtualMachinePlan', + ), + migrations.DeleteModel( + name='VirtualMachineType', + ), + ] diff --git a/hosting/migrations/0039_hostingorder_price.py b/hosting/migrations/0039_hostingorder_price.py new file mode 100644 index 00000000..0d4945fa --- /dev/null +++ b/hosting/migrations/0039_hostingorder_price.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-12 16:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0038_auto_20170512_1006'), + ] + + operations = [ + migrations.AddField( + model_name='hostingorder', + name='price', + field=models.FloatField(default=0), + preserve_default=False, + ), + ] diff --git a/hosting/migrations/0040_hostingplan.py b/hosting/migrations/0040_hostingplan.py new file mode 100644 index 00000000..cb48ccd6 --- /dev/null +++ b/hosting/migrations/0040_hostingplan.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2017-05-13 11:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0039_hostingorder_price'), + ] + + operations = [ + migrations.CreateModel( + name='HostingPlan', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('disk_size', models.FloatField(default=0.0)), + ('cpu_cores', models.FloatField(default=0.0)), + ('memory', models.FloatField(default=0.0)), + ], + ), + ] diff --git a/hosting/mixins.py b/hosting/mixins.py index 2f8de3a5..c8539eee 100644 --- a/hosting/mixins.py +++ b/hosting/mixins.py @@ -1,26 +1,20 @@ from django.shortcuts import redirect from django.core.urlresolvers import reverse -from .models import VirtualMachinePlan + +from opennebula_api.serializers import VirtualMachineTemplateSerializer +from opennebula_api.models import OpenNebulaManager class ProcessVMSelectionMixin(object): def post(self, request, *args, **kwargs): - hosting = request.POST.get('configuration') - configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(hosting) - vm_specs = { - 'cores': request.POST.get('cores'), - 'memory': request.POST.get('memory'), - 'disk_size': request.POST.get('disk_space'), - 'hosting_company': request.POST.get('hosting_company'), - 'location_code': request.POST.get('location_code'), - 'configuration': hosting, - 'configuration_detail': configuration_detail, - 'final_price': request.POST.get('final_price') - } - request.session['vm_specs'] = vm_specs + + template_id = int(request.POST.get('vm_template_id')) + template = OpenNebulaManager().get_template(template_id) + data = VirtualMachineTemplateSerializer(template).data + request.session['template'] = data + if not request.user.is_authenticated(): - request.session['vm_specs'] = vm_specs request.session['next'] = reverse('hosting:payment') return redirect(reverse('hosting:login')) return redirect(reverse('hosting:payment')) diff --git a/hosting/models.py b/hosting/models.py index e5899b0e..25f852f1 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,189 +1,57 @@ import os +import socket +import logging + +import oca from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.functional import cached_property +from django.conf import settings + from Crypto.PublicKey import RSA from stored_messages.settings import stored_messages_settings -from membership.models import StripeCustomer +from membership.models import StripeCustomer, CustomUser from utils.models import BillingAddress from utils.mixins import AssignPermissionsMixin from .managers import VMPlansManager +logger = logging.getLogger(__name__) -class VirtualMachineType(models.Model): - HETZNER_NUG = 'hetzner_nug' - HETZNER = 'hetzner' - HETZNER_R6 = 'hetzner_raid6' - HETZNER_G = 'hetzner_glusterfs' - BERN = 'bern' - DE_LOCATION = 'DE' - CH_LOCATION = 'CH' +class HostingPlan(models.Model): + disk_size = models.FloatField(default=0.0) + cpu_cores = models.FloatField(default=0.0) + memory = models.FloatField(default=0.0) - HOSTING_TYPES = ( - (HETZNER_NUG, 'Hetzner No Uptime Guarantee'), - (HETZNER, 'Hetzner'), - (HETZNER_R6, 'Hetzner Raid6'), - (HETZNER_G, 'Hetzner Glusterfs'), - (BERN, 'Bern'), - ) - - LOCATIONS_CHOICES = ( - (DE_LOCATION, 'Germany'), - (CH_LOCATION, 'Switzerland'), - ) - - description = models.TextField() - base_price = models.FloatField() - memory_price = models.FloatField() - core_price = models.FloatField() - disk_size_price = models.FloatField() - hosting_company = models.CharField(max_length=30, choices=HOSTING_TYPES) - location = models.CharField(max_length=3, choices=LOCATIONS_CHOICES) - - def __str__(self): - return "%s" % (self.get_hosting_company_display()) - - @classmethod - def get_serialized_vm_types(cls): - return [vm.get_serialized_data() - for vm in cls.objects.all()] - - def calculate_price(self, specifications): - price = float(specifications['cores']) * self.core_price - price += float(specifications['memory']) * self.memory_price - price += float(specifications['disk_size']) * self.disk_size_price - price += self.base_price - return price - - def defeault_price(self): - price = self.base_price - price += self.core_price - price += self.memory_price - price += self.disk_size_price * 10 - return price - - def get_serialized_data(self): + def serialize(self): return { - 'description': self.description, - 'base_price': self.base_price, - 'core_price': self.core_price, - 'disk_size_price': self.disk_size_price, - 'memory_price': self.memory_price, - 'hosting_company_name': self.get_hosting_company_display(), - 'hosting_company': self.hosting_company, - 'default_price': self.defeault_price(), - 'location_code': self.location, - 'location': self.get_location_display(), 'id': self.id, + 'cpu':self.cpu_cores, + 'memory': self.memory, + 'disk_size': self.disk_size, + 'price': self.price(), } - -class VirtualMachinePlan(AssignPermissionsMixin, models.Model): - - PENDING_STATUS = 'pending' - ONLINE_STATUS = 'online' - CANCELED_STATUS = 'canceled' - - VM_STATUS_CHOICES = ( - (PENDING_STATUS, 'Pending for activation'), - (ONLINE_STATUS, 'Online'), - (CANCELED_STATUS, 'Canceled') - ) - - DJANGO = 'django' - RAILS = 'rails' - NODEJS = 'nodejs' - - VM_CONFIGURATION = ( - (DJANGO, 'Ubuntu 14.04, Django'), - (RAILS, 'Ubuntu 14.04, Rails'), - (NODEJS, 'Debian, NodeJS'), - ) - - permissions = ('view_virtualmachineplan', - 'cancel_virtualmachineplan', - 'change_virtualmachineplan') - - cores = models.IntegerField() - memory = models.IntegerField() - disk_size = models.IntegerField() - vm_type = models.ForeignKey(VirtualMachineType) - price = models.FloatField() - public_key = models.TextField(blank=True) - status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS) - ip = models.CharField(max_length=50, blank=True) - configuration = models.CharField(max_length=20, choices=VM_CONFIGURATION) - - objects = VMPlansManager() - - class Meta: - permissions = ( - ('view_virtualmachineplan', 'View Virtual Machine Plan'), - ('cancel_virtualmachineplan', 'Cancel Virtual Machine Plan'), - ) - - def __str__(self): - return self.name - - @cached_property - def hosting_company_name(self): - return self.vm_type.get_hosting_company_display() - - @cached_property - def location(self): - return self.vm_type.get_location_display() - - @cached_property - def name(self): - name = 'vm-%s' % self.id - return name - - @cached_property - def notifications(self): - stripe_customer = StripeCustomer.objects.get(hostingorder__vm_plan=self) - backend = stored_messages_settings.STORAGE_BACKEND() - messages = backend.inbox_list(stripe_customer.user) - return messages - @classmethod - def create(cls, data, user): - instance = cls.objects.create(**data) - instance.assign_permissions(user) - return instance - - @staticmethod - def generate_RSA(bits=2048): - ''' - Generate an RSA keypair with an exponent of 65537 in PEM format - param: bits The key length in bits - Return private key and public key - ''' - new_key = RSA.generate(2048, os.urandom) - public_key = new_key.publickey().exportKey("OpenSSH") - private_key = new_key.exportKey("PEM") - return private_key, public_key - - def generate_keys(self): - private_key, public_key = self.generate_RSA() - self.public_key = public_key - self.save(update_fields=['public_key']) - return private_key, public_key - - def cancel_plan(self): - self.status = self.CANCELED_STATUS - self.save(update_fields=['status']) + def get_serialized_configs(cls): + return [cfg.serialize() + for cfg in cls.objects.all()] + def price(self): + price = self.disk_size * 0.6 + price += self.cpu_cores * 5 + price += self.memory * 2 + return price class HostingOrder(AssignPermissionsMixin, models.Model): ORDER_APPROVED_STATUS = 'Approved' ORDER_DECLINED_STATUS = 'Declined' - vm_plan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders') + vm_id = models.IntegerField(default=0) customer = models.ForeignKey(StripeCustomer) billing_address = models.ForeignKey(BillingAddress) created_at = models.DateTimeField(auto_now_add=True) @@ -191,6 +59,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): last4 = models.CharField(max_length=4) cc_brand = models.CharField(max_length=10) stripe_charge_id = models.CharField(max_length=100, null=True) + price = models.FloatField() permissions = ('view_hostingorder',) @@ -207,9 +76,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model): return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS @classmethod - def create(cls, vm_plan=None, customer=None, billing_address=None): - instance = cls.objects.create(vm_plan=vm_plan, customer=customer, - billing_address=billing_address) + def create(cls, price=None, vm_id=None, customer=None, billing_address=None): + instance = cls.objects.create( + price=price, + vm_id=vm_id, + customer=customer, + billing_address=billing_address + ) instance.assign_permissions(customer.user) return instance @@ -223,14 +96,55 @@ class HostingOrder(AssignPermissionsMixin, models.Model): self.cc_brand = stripe_charge.source.brand self.save() + def get_cc_data(self): + return { + 'last4': self.last4, + 'cc_brand': self.cc_brand, + } if self.last4 and self.cc_brand else None +class UserHostingKey(models.Model): + user = models.ForeignKey(CustomUser) + public_key = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + name = models.CharField(max_length=100) + @staticmethod + def generate_RSA(bits=2048): + ''' + Generate an RSA keypair with an exponent of 65537 in PEM format + param: bits The key length in bits + Return private key and public key + ''' + new_key = RSA.generate(2048, os.urandom) + public_key = new_key.publickey().exportKey("OpenSSH") + private_key = new_key.exportKey("PEM") + return private_key, public_key + @classmethod + def generate_keys(cls): + private_key, public_key = cls.generate_RSA() + # self.public_key = public_key + # self.save(update_fields=['public_key']) + return private_key, public_key +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 diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index c75e3d48..d06a5a3d 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -25,6 +25,27 @@ $( document ).ready(function() { }); + var hasCreditcard = window.hasCreditcard || false; + console.log("has creditcard", hasCreditcard); + // hasCreditcard= true; + + var submit_form_btn = $('#payment_button_with_creditcard'); + submit_form_btn.on('click', submit_payment); + + + function submit_payment(e){ + e.preventDefault(); + console.log("creditcard sdasd"); + // if (hasCreditcard) { + $('#billing-form').submit(); + console.log("has creditcard2"); + // } + + // $form.submit(); + } + + + var $form = $('#payment-form'); $form.submit(payWithStripe); diff --git a/hosting/templates/hosting/base_short.html b/hosting/templates/hosting/base_short.html index 1eacd26a..c6d1772e 100644 --- a/hosting/templates/hosting/base_short.html +++ b/hosting/templates/hosting/base_short.html @@ -72,6 +72,11 @@ {% trans "My Orders"%} +
  • + + {% trans "Keys"%} + +
  • {% trans "Notifications "%} diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html new file mode 100644 index 00000000..e34b2f11 --- /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 0900 0000 6071 8848 8" %} +
    +
    + {% 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 %} + +
    + + +
    + +{% endblock %} diff --git a/hosting/templates/hosting/create_virtual_machine.html b/hosting/templates/hosting/create_virtual_machine.html new file mode 100644 index 00000000..af618740 --- /dev/null +++ b/hosting/templates/hosting/create_virtual_machine.html @@ -0,0 +1,56 @@ +{% extends "hosting/base_short.html" %} +{% load staticfiles bootstrap3 i18n %} +{% block content %} +
    +
    +
    +
    +
    +
    + {% if messages %} +
    + {% for message in messages %} + {{ message }} + {% endfor %} +
    + {% endif %} +
    + {% if not error %} +

    {% trans "New Virtual Machine"%}

    +
    +
    + {% csrf_token %} +
    + Select VM Template: + +
    +
    + Select VM Configuration: + +
    +
    + +
    +
    + {% endif %} + +
    + +
    +
    + +
    + +{%endblock%} diff --git a/hosting/templates/hosting/includes/_pricing.html b/hosting/templates/hosting/includes/_pricing.html index 92033be8..c1b2fa11 100644 --- a/hosting/templates/hosting/includes/_pricing.html +++ b/hosting/templates/hosting/includes/_pricing.html @@ -24,58 +24,17 @@ {% csrf_token %} +
      -
    • - -

      {{vm.location_code}}

      -
      - - -
    • +
    • - - {{vm.location}} -
      -
      -
    • -
    • - - {% if select_configuration %} - - {% else %} - - - -
      -
      - - {{configuration_detail}} -
      -
      - {% endif %} -
    • -
    • - -
      -
      - - +
      @@ -83,30 +42,28 @@
    • - - - GiB +
    • - - - GiB +
    • +
    • - -

      {{vm.default_price|floatformat}}CHF

      + +

      {{vm.price|floatformat}} CHF

      per month
    • diff --git a/hosting/templates/hosting/managevms.html b/hosting/templates/hosting/managevms.html new file mode 100644 index 00000000..54270316 --- /dev/null +++ b/hosting/templates/hosting/managevms.html @@ -0,0 +1,48 @@ +{% extends "admin/base_site.html" %} +{% block content %} + +
      + {% csrf_token %} + {{ form }} + +
      +{% if vms %} +
      + + + + + + + + + + + + +{% for vm in vms %} + + + + + + + + +{% endfor %} + +
      IDNameMemoryStatusUser NameActions
      {{vm.id}}{{vm.name}}{{vm.template.memory}}{{vm.str_state}}{{vm.uname}} +{% if vm.str_state == 'ACTIVE' %} +Stop VM +{% elif vm.str_state == 'STOPPED' %} +   Start VM +{% endif %} +   Delete VM + + +
      +
      +{% else %} +

      You do not have any VM.

      +{% endif %} +{% endblock %} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 09e81ba2..868680aa 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -49,23 +49,19 @@

      {% trans "Order summary"%}


      -

      {% trans "Type"%} {{order.vm_plan.hosting_company_name}}

      +

      {% trans "Cores"%} {{vm.cores}}


      -

      {% trans "Configuration"%} {{order.vm_plan.get_configuration_display}}

      +

      {% trans "Memory"%} {{vm.memory}} GiB


      -

      {% trans "Cores"%} {{order.vm_plan.cores}}

      +

      {% trans "Disk space"%} {{vm.disk_size}} GiB


      -

      {% trans "Memory"%} {{order.vm_plan.memory}} GiB

      -
      -

      {% trans "Disk space"%} {{order.vm_plan.disk_size}} GiB

      -
      -

      {% trans "Total"%}

      {{order.vm_plan.price}} CHF

      +

      {% trans "Total"%}

      {{vm.price}} CHF


      {% url 'hosting:payment' as payment_url %} {% if payment_url in request.META.HTTP_REFERER %} {% endif %}
  • diff --git a/hosting/templates/hosting/orders.html b/hosting/templates/hosting/orders.html index 7d1009ea..16a739a0 100644 --- a/hosting/templates/hosting/orders.html +++ b/hosting/templates/hosting/orders.html @@ -25,7 +25,7 @@ {{ order.id }} {{ order.created_at }} - {{ order.vm_plan.price }} CHF + {{ order.price }} CHF {% if order.approved %} {% trans "Approved"%} {% else %} @@ -99,4 +99,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/hosting/templates/hosting/payment.html b/hosting/templates/hosting/payment.html index 6dd711ab..459dcf0b 100644 --- a/hosting/templates/hosting/payment.html +++ b/hosting/templates/hosting/payment.html @@ -8,7 +8,7 @@

    Billing Address


    -
    + {% for field in form %} {% csrf_token %} {% bootstrap_field field show_label=False type='fields'%} @@ -23,6 +23,17 @@
    + {% if credit_card_data.last4 %} + +
    Credit Card
    +
    Last 4: *****{{credit_card_data.last4}}
    +
    Type: {{credit_card_data.cc_brand}}
    + + + + {% else %} + +
    @@ -76,6 +87,7 @@ + {% endif %}
    @@ -86,17 +98,19 @@

    Billing Amount


    -

    Type {{request.session.vm_specs.location_code}}

    + + +

    Cores {{request.session.specs.cpu|floatformat}}


    -

    Cores {{request.session.vm_specs.cores}}

    +

    Memory {{request.session.specs.memory|floatformat}} GiB


    -

    Configuration {{request.session.vm_specs.configuration_detail}}

    -
    -

    Memory {{request.session.vm_specs.memory}} GiB

    +

    Disk space {{request.session.specs.disk_size|floatformat}} GiB


    -

    Disk space {{request.session.vm_specs.disk_size}} GiB

    -
    -

    Total

    {{request.session.vm_specs.final_price}} CHF

    +

    Total

    {{request.session.specs.price }} CHF

    @@ -110,6 +124,7 @@ + {% if stripe_key %} {%endif%} +{% if credit_card_data.last4 and credit_card_data.cc_brand %} + + +{%endif%} + {%endblock%} diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index e0909b24..0b17878d 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -25,12 +25,6 @@ {% trans "Billing"%} -
  • - - - {% trans "Orders"%} - -
  • {% trans "Status"%} @@ -85,7 +79,7 @@
    {% trans "Disk"%}
    - {{virtual_machine.disk_size}} GiB + {{virtual_machine.disk_size|floatformat:2}} GiB
    @@ -93,7 +87,7 @@
    - {% trans "Configuration"%}: {{virtual_machine.get_configuration_display}} + {% trans "Configuration"%}: {{virtual_machine.configuration}}
    @@ -109,54 +103,21 @@ -
    -
    -
    - -

    Orders

    -
    - - - - - - - - - - - {% for order in virtual_machine.hosting_orders.all %} - - - - - - - - {% endfor %} - -
    #{% trans "Date"%}{% trans "Amount"%}{% trans "Status"%}
    {{order.id}}{{order.created_at}}{{order.vm_plan.price}} CHF{% if order.approved %} - {% trans "Approved"%} - {% else%} - {% trans "Declined"%} - {% endif%} - - -
    -
    -
    -

    {% trans "Current status"%}

    +
    - {% if virtual_machine.status == 'pending' %} - {{virtual_machine.get_status_display}} - {% elif virtual_machine.status == 'online' %} - {{virtual_machine.get_status_display}} - {% elif virtual_machine.status == 'canceled'%} - {{virtual_machine.get_status_display}} + {% if virtual_machine.state == 'PENDING' %} + Pending + {% elif virtual_machine.state == 'ACTIVE' %} + Online + {% elif virtual_machine.state == 'FAILED'%} + Failed {% endif %}
    @@ -165,23 +126,36 @@
    -
    + {% csrf_token %}
    - +
    +
    +
    +
    + {% if messages %} +
    + {% for message in messages %} + {{ message }} + {% endfor %} +
    + {% endif %} +
    +